sexta-feira, 6 de julho de 2007

Autenticação em Windows Form e controle de acesso a métodos.

Ola pessoal, andei sumido com alguns problemas no PC e alguma falta de tempo, mas hoje eu vou falar de um assunto que não vejo ser muito explorado talvez pela moda de tudo ser aplicação web, nada contra elas, muito pelo contrario eu gosto muito delas mas que há uma certa moda de fazer aplicações web para vender uma imagem de “moderno” isto tem, mas o assunto não e este, o assunto e autenticação para Windows form, pretendo explorar a infra-estrutura de segurança do .Net Framework.

Coisa muito simples basicamente, vamos autenticar o nosso usuário normalmente como você já faz sendo que iremos informar a aplicação deste resultado e deixar que ela gerencie este acesso de acordo com o desejado. Para isto vamos fazer uso de duas interfaces do .Net que são IIdentity e IPrincipal,então mão na massa.

Primeiro vamos criar uma classe que eu chamei de Identificacao nesta classe vamos implementar a interface IIdentity, esta classe sera justamente isto uma identidade e a interface implementa o que uma identidade do sistema tem que ter. o codigo da implementação da interface e este logo a baixo da declaração da classe.

Public Class Identificacao
Implements System.Security.Principal.IIdentity

Agora o visual studio deve ter criado os metodos da interface vamos tratar de um de cada vez.

O primeiro deles e o AuthenticationType, para nos ele não e muito importante para a eutenticação em si, ele e apenas o tipo de autenticação usada, e util se vc criar varios tipos de autenticação e quiser disquir os tipo que você criou.eu coloquei o um texto qualquer pro exemplo da seguinte forma.

Public ReadOnly Property AuthenticationType() As String Implements System.Security.Principal.IIdentity.AuthenticationType
Get
Return "Autenticação Windows Form Personalizada"
End Get
End Property

Depois vem o IsAuthenticated, este metodo e o metodo que ira informar se esta identificação e autenticada ou não então criamos uma variavel privada _ IsAuthenticated do tipo booleano para ser o retorno e o codigo da propriedade fica assim.

Public ReadOnly Property IsAuthenticated() As Boolean Implements System.Security.Principal.IIdentity.IsAuthenticated
Get
Return _IsAuthenticated
End Get
End Property

Por ultimo vem o Name que como vocÊ já deve esta imaginando e o nome do usuario, como em IsAuthenticated nos criamos uma variavel privada _name do tipo string para o retorno da propriedade que ficou assim.

Public ReadOnly Property Name() As String Implements System.Security.Principal.IIdentity.Name
Get
Return _Name
End Get
End Property


Bem, agora que resolvemos os métodos da interface vamos criar mais algumas coisas que vão nos ajudar na autenticação, primeiro vamos criar uma propriedade Role que vai nos auxiliar para identificar o papel do nosso usuário ela será do tipo BuiltInRole que um Enum que lista os papais gerenciados pelo .Net. para isto vamos criar uma variavel privada _Role para o retorno , o codigo da propriedade fica como abaixo.

Public ReadOnly Property Role() As ApplicationServices.BuiltInRole
Get
Return _Role
End Get
End Property

Agora vamos criar uma função para autenticar o nosso usuario, aqui não vou entrar em meritos de banco segurança e outras coisas por se um exemplo, na verdade aqui você poderar copiar e colar a função que você usa para autenticar normalmente deste que com ela você consiga preencher as informações da indentificação no passo seguinte, vale a pena dar uma olhada no help que tem um exemplo bem bacana com hash la.Mas aqui vamos por um codigo direto para autenticar João e Maria onde João e o Administrador e Maria e um usuario comum. Vou por a função para retornar a Role que para o exemplo e mais simples, mas nada o impede de retornar o verdadeiro ou falso ou até mesmo uma estrutura já que no mundo real isto veria de um banco e com mais informações provavelmente. O código de autenticação esta ai.

Private Function IsValidLogin(ByVal Login As String, ByVal PassWord As String) As ApplicationServices.BuiltInRole
Dim saida As ApplicationServices.BuiltInRole
If Login = "joao" And PassWord = "12345" Then
saida = ApplicationServices.BuiltInRole.Administrator
ElseIf Login = "maria" And PassWord = "54321" Then
saida = ApplicationServices.BuiltInRole.User
Else
saida = ApplicationServices.BuiltInRole.Guest
End If
Return saida
End Function

Agora vamos fazer o nosso construtor que terá como argumentos o Login e a Senha do nosso usuário e fará a chamada a nossa função de autenticação.O código e bem simples, se a função retornar um guest vamos assumir que ele não foi autenticado já que não nos interessa aqui usuários visitantes o IsAuthenticated e igual a falso e o name e em vazio , do contrario setamos o IsAuthenticated como true e o Name como o login do usuario.

Sub New(ByVal Login As String, ByVal PassWord As String)
_Role = IsValidLogin(Login, PassWord)
If _Role = ApplicationServices.BuiltInRole.Guest Then
_IsAuthenticated = False
_Name = ""
Else
_IsAuthenticated = True
_Name = Login
End If
End Sub


Com isto a nosso classe de identidade esta pronta, agora temos que fazer com o que o sistema a reconheça, para isto temos que criar mas uma classe que vai implementar a interface IPrincipal,a chamaremos de PrincipalIdentificacao ela sera responsavel por incapsular a nosso intentificação para que seja informada ao aplicativo que ela e a identificação principal do usuario do sistema. A declaração da implementação dela e como se segue.

Implements System.Security.Principal.IPrincipal

Você deve ter notadado que foi declarada uma propriedade Identity que e do tipo IIdentity, que não por coincidência e do mesmo tipo que a nossa classe identificacao.E ela que ira retornar a nossa identificação pra o framework, para isto vamos criar uma variavel privada da nossa classe para o retorno da propriedade logo abaixo do implements escrea a declação a baixo.

Private _Identity As Identificacao

E autere a propriedade identity de maneira que fique assim.

Public ReadOnly Property Identity() As System.Security.Principal.IIdentity Implements System.Security.Principal.IPrincipal.Identity
Get
Return _Identity
End Get
End Property

O metodo IsRole acho que o nome já e autodescrtivo e uma função que testa se o usuario e da role que foi informada retornando um verdadeiro ou falso o codigo e seguinte.

Public Function IsInRole(ByVal role As String) As Boolean Implements System.Security.Principal.IPrincipal.IsInRole
Return role = _Identity.Role.ToString
End Function

E agora vamos criar um construtor para entrarmos com o login e a senha como argumentos, o construtor só ira instanciar uma itentificação com basse nestes parametros.

Sub New(ByVal Login As String, ByVal PassWord As String)
_Identity = New Identificacao(Login, PassWord)
End Sub


Pronto com isto já temos a infra-esturura para autenticação, construar agora uma janela de login normal o de sempre dois textbox dois botôes o de constume.E crie um form para ser acessado casso o login seja realizado com sucesso eu chamei este form de frmMetodosTesteAcesso

De um duplo click no botão do ok e escreva o seguinte.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Autenticacao As New PrincipalIdentificacao(Me.txtlogin.Text, Me.txtsenha.Text)
If Autenticacao.Identity.IsAuthenticated = True Then
Threading.Thread.CurrentPrincipal = Autenticacao
Dim form As New frmMetodosTesteAcesso
form.Show()
Else
MessageBox.Show("Login ou Senha invalida, usuario não autorizado", "Falha de Login", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End If
End Sub

Vamos la explicando, criamos uma instancia de PrincipalIdentificacao passando o login e a senha no nosso usuario,então testamos se este usuario conseguiu se altenticar conforme o que foi programado em nossa classe identificacao que e ela quem estamos acessando atraves da propriedade Identity, em caso afirmativo substituimos a indentificação do usuario da thead principal do aplicativo pela nossa identificação, e abrimos o formulario se não negamos o acesso.

A esta autura você deve esta se perguntando, nossa tanto trabalho so para isto?? , calma pessoa de pouca fé, a melhor parte vem agora.

Com isto agora podemos ter acesso as informações da indentificação do usuario logado em qualquer parte da nossa aplicação. Para demostrar isto vamos até o frmMetodosTesteAcesso, crie dois botão nele.

Va au codigo do formulario e vamos criar dois funções simples so pra teste, vamos criar a primeira MessagemADM que só deverar ser executada por usuario admistrador. E para isto vamos usar um decoração de tag no na função. Da seguinte forma.

_
Function MenssagemADM() As String
Return "adiministrador"
End Function

O que eu digo aqui e que a permisão sera dada por demanda aos usuarios que tiverem papel de administrador.

E agora vamos criar uma outra que será executada por todo usuario autenticado.

_
Function MenssagemUSE() As String
Return "Usuario"
End Function

Aqui tambem a permisão sera dada por demanda a todo usuario autenticado.

Agora sim no primeiro botão que chamaremos de btnAdm vamos escrever o seguinte codigo.

Sub BtnAdm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdm.Click
Try
MessageBox.Show(MenssagemADM)
Catch
MessageBox.Show("Negado")
End Try
End Sub

E no outro botão chamado btnUser faremos um codigo igual só que chamando a função MenssagemUSE

Private Sub btnUser_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUser.Click
Try
MessageBox.Show(MenssagemUSE)
Catch
MessageBox.Show("Negado")
End Try
End Sub


Percebeu ?, com este codigo joão que e adminstrador pode executar os dois metodos, mas maria não só pode executar o de usuario por ser autenticada, e só atribuir as permissões aos usuarios e dizer aos metodos quem pode fazer o que, muito melhor do os milhares de codigos loucos que tem por ai com listas em tabelas de quem pode o que e a onde ou cases selects infinitos.Os cuidados aqui são apenas dois o primeiro deles e na tag de autenticação que e case sencitive e que a chamada dos metodos controlados sempre devem ser entre try/catch por que quando e feita a tentativa de acesso a um metodo que não e permidido e lancado um erro. Valeu pessoal e até a proxima.

domingo, 10 de junho de 2007

Criando Relatórios com PrintDocument

Ola, vou falar hoje de criação de relatórios com PrintDocument, este e um componente para criação de relatórios que é muito simples de usar, só exigindo que o desenvolvedor tenha uma dose de atenção extra , já que o desenvolvedor tem que definir cada um dos elementos que serão impressos como também a sua posição.Mas particularmente eu prefiro este componente ao Crystal Report, por que o Crystal e sim muito fácil de fazer um relatório com ele, mas até hoje a cada nova instalação na maquina do cliente e uma aventura com os ocx dll e outras coisas que o Crystal depende mesmo no .Net, o que faz o Crystal ser uma opção viável apenas para ambientes muito bem controlados, o que não e muito fácil para quem faz programas comerciais que serão distribuídos para os mais diversos ambientes.

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)("NOME"), Me.Fonte, New System.Drawing.SolidBrush(Color.Black), Me.X, Me.Y)


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)("NOME"), Me.Fonte, New System.Drawing.SolidBrush(Color.Black), Me.X, Me.Y)


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.




Todo relatorio,tem que ter cabeçalho e rodape, então agora que já vimos o coração do codigo vamos infeitar um pouco,vamos começar pelo cabeçalho.

Vamos criar uma procedure, para o cabeçalho para o codigo não ficar muito bagunçado, passando o eventargs como parametro.Vou colocar o codigo e vou comentando os trechos


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: Nome:", New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.Blue), Me.X, Me.Y)


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: Nome:", New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.Blue), Me.X, Me.Y)

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;amp; " - " & Me.tbDados.Rows(Me.Registro)("NOME"), Me.Fonte, New System.Drawing.SolidBrush(Color.Black), Me.X, Me.Y)

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.



Codigo para criação de relatórios com PrintDocument

Public Class Form2

Private _PaginaAtual As Integer

Private _X As Single

Private _Y As Single

Private _MargemSuperior As Single

Private _Registro As Integer

Private _Fonte As System.Drawing.Font

Private tbDados As DataTable

Private _AlturaDoCabecalho As Single

Property AlturaDoCabecalho() As Single

Get

Return _AlturaDoCabecalho

End Get

Set(ByVal value As Single)

_AlturaDoCabecalho = value

End Set

End Property

Private Property Registro() As Integer

Get

Return _Registro

End Get

Set(ByVal value As Integer)

_Registro = value

End Set

End Property

Property MargemSuperior() As Single

Get

Return _MargemSuperior

End Get

Set(ByVal value As Single)

_MargemSuperior = value

End Set

End Property

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 + Me.MargemSuperior

End Get

Set(ByVal value As Single)

_Y = value - Me.MargemSuperior

End Set

End Property

Public Property PaginaAtual() As Integer

Get

Return _PaginaAtual

End Get

Set(ByVal value As Integer)

_PaginaAtual = value

End Set

End Property

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

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") & " - " & Me.tbDados.Rows(Me.Registro)("NOME"), Me.Fonte, New System.Drawing.SolidBrush(Color.Black), Me.X, Me.Y)

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)

Me.Y = Me.MargemSuperior

e.HasMorePages = True

Exit Sub

End If

End While

Me.Rodape(e)

e.HasMorePages = False

End Sub

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

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Me.Registro = 0

Me.PaginaAtual = 0

Me.PrintPreviewDialog1.Document = Me.PrintDocument1

Me.PrintPreviewDialog1.ShowDialog()

End Sub

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: Nome:", New Font("Verdana", 10, FontStyle.Bold), New System.Drawing.SolidBrush(Color.Blue), Me.X, Me.Y)

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

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

End Class