10 08 2016
Imprimindo conteúdo do DataGridView no Crystal Reports
Você que utiliza o DataGridView para entrada de dados no seu sistema Windows Forms sem necessariamente estar ligado a algum banco de dados, já pensou em imprimir as informações do grid? Uma opção é imprimir o controle em si, mas, muito provavelmente essa não é a opção que traz uma melhor experiência para o usuário. Que tal imprimir o conteúdo do DataGridView no Crystal Reports? Se essa ideia te interessou, continue lendo esse artigo para conferir como implementar essa funcionalidade.
Criando o formulário com DataGridView
Primeiramente, vamos começar criando um novo projeto do tipo “Windows Forms Application“. Na verdade, poderíamos utilizar a mesma lógica deste artigo em outros tipos de projeto (como WPF, Web Forms ou MVC). Só escolhi o Windows Forms por ser mais fácil de demonstrar e por ser o mais conhecido no mercado.
Uma vez criado o projeto, vamos ajustar o formulário, adicionando um DataGridView e algumas colunas (com os nomes “FuncionarioID“, “Nome” e “Sobrenome“). Além disso, vamos colocar um botão logo abaixo do grid que servirá para chamarmos o outro formulário que apresentará o relatório:
Após esse ponto, se executarmos a aplicação, veremos que o grid está funcional, ou seja, podemos adicionar algumas linhas:
E agora? Como criar o relatório?
Uma vez estando o DataGridView já preparado e funcional, vamos criar um novo relatório do Crystal Reports. Dê o nome de “RelatorioFuncionario” para o novo relatório que será criado e escolha a opção de gerar o relatório em branco:
Agora é que surge o problema: como é que escolhemos uma fonte de dados para o relatório? Se olharmos na janela “Database Expert” do Crystal Reports, não encontraremos nenhum DataSet ou classe de dados para utilizarmos no nosso relatório:
E agora? Como desenhamos o relatório sem termos um DataSet ou classe de dados no nosso projeto? A verdade é que é possível gerar a definição de campos manualmente no Crystal Reports através de arquivos TTX, porém, eu não recomendo. A razão para isso é que, mesmo que você não utilize um DataSet ou classe para fazer o design do relatório, você obrigatoriamente terá que passar um DataSet ou coleção de objetos para alimentar o relatório do Crystal Reports (essas são as duas únicas opções de passar dados para o Crystal Reports através de aplicações .NET). Dessa forma, já que teremos que criar um DataSet ou classe para passar os dados para o Crystal Reports, compensa muito mais utilizá-los na hora da definição do relatório.
Dessa forma, vamos ver a seguir como criar um DataSet e como criar uma classe para desenharmos o relatório e alimentarmos com as linhas cadastradas no grid.
Criando o relatório com um DataSet
Primeiramente, vamos ver como podemos criar um DataSet com as informações cadastradas no grid. Já que vamos criar um DataSet, vamos cria-lo como DataSet tipado. Para isso, adicione um novo item ao projeto utilizando o tipo “DataSet” (que está localizado dentro da categoria “Data“). Dê o nome de “DataSetRelatorio” para o novo DataSet e adicione uma nova tabela chamada “Funcionario“:
Em seguida, se voltarmos para a tela de “Database Expert” no Crystal Reports, veremos que o DataSet estará disponível para a utilização no relatório:
Adicione essa tabela ao relatório e trabalhe no design dele de forma que ele fique parecido com a imagem abaixo:
OK. Agora que já temos o relatório desenhado, vamos criar um novo formulário para exibi-lo. Para isso, adicione um novo formulário no projeto, dando o nome de “FormRelatorio“. Dentro desse formulário, adicione um controle do Crystal Reports e selecione o relatório que acabamos de desenhar:
Logo em seguida, vamos até o code-behind do formulário para adicionarmos um novo construtor recebendo o DataSet que alimentará o relatório:
// C# public FormRelatorio(DataSet dataSet) : this() { RelatorioFuncionario1.SetDataSource(dataSet); crystalReportViewer1.RefreshReport(); }
' VB.NET Public Sub New() ' This call is required by the designer. InitializeComponent() End Sub Public Sub New(DataSet As DataSet) Me.New() RelatorioFuncionario1.SetDataSource(DataSet) CrystalReportViewer1.RefreshReport() End Sub
Feito isso, agora podemos voltar para o nosso formulário inicial para implementarmos a ação do botão “Relatório“. Como temos que passar um DataSet para o formulário de relatórios, teremos que fazer um “foreach” nas linhas do grid para alimentarmos uma instância do DataSet que criamos anteriormente:
// C# //Opção 1 - Criando DataSet "na mão": var dataSet = new DataSetRelatorio(); foreach (DataGridViewRow linha in dataGridView1.Rows) { if (!linha.IsNewRow) { var novaLinhaDataSet = dataSet.Funcionario.NewFuncionarioRow(); novaLinhaDataSet.FuncionarioID = linha.Cells["FuncionarioID"].Value.ToString(); if (linha.Cells["Nome"].Value != null) novaLinhaDataSet.Nome = linha.Cells["Nome"].Value.ToString(); if (linha.Cells["Sobrenome"].Value != null) novaLinhaDataSet.Sobrenome = linha.Cells["Sobrenome"].Value.ToString(); dataSet.Funcionario.AddFuncionarioRow(novaLinhaDataSet); } } var formRelatorio = new FormRelatorio(dataSet); formRelatorio.Show();
' VB.NET ' Opção 1 - Criando DataSet "na mão" Dim DataSet As New DataSetRelatorio() For Each Linha As DataGridViewRow In dataGridView1.Rows If Not Linha.IsNewRow Then Dim NovaLinhaDataSet = DataSet.Funcionario.NewFuncionarioRow() NovaLinhaDataSet.FuncionarioID = Linha.Cells("FuncionarioID").Value.ToString() If Linha.Cells("Nome").Value <> Nothing Then NovaLinhaDataSet.Nome = Linha.Cells("Nome").Value.ToString() End If If Linha.Cells("Sobrenome").Value <> Nothing Then NovaLinhaDataSet.Sobrenome = Linha.Cells("Sobrenome").Value.ToString() End If DataSet.Funcionario.AddFuncionarioRow(NovaLinhaDataSet) End If Next Dim FormRelatorio As New FormRelatorio(DataSet) FormRelatorio.Show()
Execute a aplicação, adicione algumas linhas no grid, clique no botão “Relatório” e veja que os dados digitados no grid serão passados para o relatório, justamente como esperado:
Nota: se você receber uma “FileNotFoundException” vindo do controle do Crystal Reports ao executar o projeto, isso quer dizer que falta uma configuração no seu arquivo app.config. Eu mostrei como fazer essa configuração no artigo: Trabalhando com o Crystal Reports no WPF.
Apesar dessa metodologia ter funcionado, ela não é a mais indicada. Como temos que criar o DataSet de qualquer maneira para alimentarmos o relatório, seria muito mais vantajoso fazermos o databinding do DataSet com o grid (ao invés de trabalhar sem databinding e ter que criar o DataSet “na mão” antes de exibir o relatório).
O processo de “bindar” o DataSet com o grid é muito simples. Basicamente, nós temos que criar uma nova instância do DataSet no nível do formulário e, no construtor, temos que utilizar essa instância como “DataSource” do DataGridView. Não podemos esquecer de configurarmos a propriedade “AutoGenerateColumns” como “false” (uma vez que nós já criamos as colunas manualmente no DataGridView) e também temos que configurar a propriedade “DataMember” como “Funcionario” (que é o nome da DataTable):
// C# public partial class FormFuncionarios : Form { DataSetRelatorio _dataSet = new DataSetRelatorio(); public FormFuncionarios() { InitializeComponent(); // Opção 2 - DataSet "bindado" no DataGridView dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = _dataSet; dataGridView1.DataMember = "Funcionario"; } }
' VB.NET Public Class FormFuncionarios Private DataSet As New DataSetRelatorio Public Sub New() ' This call is required by the designer. InitializeComponent() ' Opção 2 - DataSet "bindado" no DataGridView dataGridView1.AutoGenerateColumns = False dataGridView1.DataSource = DataSet dataGridView1.DataMember = "Funcionario" End Sub End Class
Com isso, ao invés de termos que criar um novo DataSet para passarmos para o relatório, podemos utilizar esse DataSet que acabamos de criar no nível do formulário. Como ele está “bindado” com o grid, todas as linhas que forem criadas no grid serão passadas para o relatório. Nesse caso, o código para exibirmos o formulário do relatório fica muito mais simples:
// C# // Opção 2 - DataSet "bindado" no DataGridView var formRelatorio = new FormRelatorio(_dataSet); formRelatorio.Show();
' VB.NET ' Opção 2 - DataSet "bindado" no DataGridView Dim FormRelatorio = New FormRelatorio(DataSet) FormRelatorio.Show()
Atenção! Pode ser que você receba um erro parecido com a imagem abaixo ao tentar criar uma nova linha no grid:
Se isso acontecer, significa que ficou faltando configurar a propriedade “DataPropertyName” nas colunas no grid. Essa propriedade serve para ligar as colunas do grid com as colunas da DataTable:
Criando o relatório com uma classe
Agora que já vimos como exibimos o relatório do Crystal Reports utilizando um DataSet, vamos conferir como podemos alimentar o mesmo relatório utilizando instâncias de uma classe? Para isso, vamos adicionar uma nova classe no nosso projeto. Essa classe terá a mesma estrutura da DataTable que criamos anteriormente:
// C# public class DadosRelatorio { public string FuncionarioID { get; set; } public string Nome { get; set; } public string Sobrenome { get; set; } }
' VB.NET Public Class DadosRelatorio Public Property FuncionarioID As String Public Property Nome As String Public Property Sobrenome As String End Class
Com essa nova classe criada, na hora de exibirmos o relatório nós teremos que iterar pelas linhas do grid montando uma lista de instâncias dessa classe baseada nas informações cadastradas no grid. Porém, antes disso, temos que adicionar um construtor no formulário do relatório. Esse novo construtor será muito parecido com o anterior (que recebe um DataSet), mas, nesse caso, ele deverá receber uma coleção (IEnumerable) que alimentará o relatório:
// C# public FormRelatorio(System.Collections.IEnumerable colecao) : this() { RelatorioFuncionario1.SetDataSource(colecao); crystalReportViewer1.RefreshReport(); }
' VB.NET Public Sub New(Colecao As IEnumerable) Me.New() RelatorioFuncionario1.SetDataSource(Colecao) CrystalReportViewer1.RefreshReport() End Sub
Por fim, vamos ajustar o código do botão “Relatório” para criar uma lista de “DadosRelatorio“, passando-a para o “FormRelatorio“:
// C# // Opção 3 - Criando uma lista "na mão": var lista = new List<DadosRelatorio>(); foreach (DataGridViewRow linha in dataGridView1.Rows) { if (!linha.IsNewRow) { var novoItem = new DadosRelatorio(); novoItem.FuncionarioID = linha.Cells["FuncionarioID"].Value.ToString(); if (linha.Cells["Nome"].Value != null) novoItem.Nome = linha.Cells["Nome"].Value.ToString(); if (linha.Cells["Sobrenome"].Value != null) novoItem.Sobrenome = linha.Cells["Sobrenome"].Value.ToString(); lista.Add(novoItem); } } var formRelatorio = new FormRelatorio(lista); formRelatorio.Show();
' VB.NET ' Opção 3 - Criando uma lista "na mão": Dim Lista As New List(Of DadosRelatorio) For Each Linha As DataGridViewRow In dataGridView1.Rows If Not Linha.IsNewRow Then Dim NovoItem = New DadosRelatorio() NovoItem.FuncionarioID = Linha.Cells("FuncionarioID").Value.ToString() If Linha.Cells("Nome").Value <> Nothing Then NovoItem.Nome = Linha.Cells("Nome").Value.ToString() End If If Linha.Cells("Sobrenome").Value <> Nothing Then NovoItem.Sobrenome = Linha.Cells("Sobrenome").Value.ToString() End If Lista.Add(NovoItem) End If Next Dim FormRelatorio As New FormRelatorio(Lista) FormRelatorio.Show()
Da mesma forma que podemos “bindar” um DataSet com o grid, nós podemos também “bindar” uma lista com o grid. Dessa forma, nós conseguimos simplificar o código da exibição do relatório, uma vez que não seria mais necessária a criação manual da lista na hora de exibirmos o relatório. Para isso, temos que criar uma “BindingList” de “DadosRelatorio” no nível do formulário e setarmos essa lista como “DataSource” do grid no construtor do formulário:
// C# public partial class FormFuncionarios : Form { BindingList<DadosRelatorio> _lista = new BindingList<DadosRelatorio>(); public FormFuncionarios() { InitializeComponent(); // Opção 4 - Lista "bindada" no DataGridView dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = _lista; } }
' VB.NET Public Class FormFuncionarios Private Lista As New System.ComponentModel.BindingList(Of DadosRelatorio) Public Sub New() ' This call is required by the designer. InitializeComponent() ' Opção 4 - Lista "bindada" no DataGridView dataGridView1.AutoGenerateColumns = False dataGridView1.DataSource = Lista End Sub End Class
Por fim, o código para exibirmos o relatório ficaria bem simples:
// C# // Opção 4 - Lista "bindada" no DataGridView var formRelatorio = new FormRelatorio(_lista); formRelatorio.Show();
' VB.NET ' Opção 4 - Lista "bindada" no DataGridView Dim FormRelatorio As New FormRelatorio(Lista) FormRelatorio.Show()
Concluindo
Apesar de ser possível criarmos a estrutura de dados de um relatório do Crystal Reports sem utilizarmos um DataSet ou classe (utilizando arquivos TTX), essa não é uma prática recomendada. Como já teremos que criar um DataSet ou classe para alimentarmos o nosso relatório, vale mais a pena criar esse DataSet ou classe antes de confeccionarmos o relatório.
Neste artigo você aprendeu a imprimir o conteúdo de um DataGridView no Crystal Reports, alimentando o relatório com um DataSet ou uma coleção de instâncias de uma classe. Você viu também que é mais recomendado “bindar” o DataSet ou a coleção diretamente com o grid ao invés de ter que construir os dados do relatório antes da sua exibição.
E você, já teve que fazer algo parecido? Como é que você acabou resolvendo essa situação? Conte mais detalhes para a gente na caixa de comentários.
Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Image by Pixabay used under Creative Commons
https://pixabay.com/en/tax-forms-income-business-468440/
Atualizando um relatório do Report Viewer de tempos em tempos Trabalhando com arquivos de recursos no C# e VB.NET
Buen día André,
Excelente explicación gracias por compartir tus conocimientos.
Saludos desde Colombia
Olá Edward! Obrigado pelo comentário! Fico feliz que tenha gostado..
Um grande abraço!
Saludos,
André Lima
Excelente artigo. Andei à procura dele a bastante tempo.
Como é que faço para implementá-lo em C#???
Olá Jocildo,
Como comentei via e-mail, os exemplos deste artigo estão apresentados tanto em C# quanto em VB.NET.. Caso você fique com alguma dúvida específica sobre a implementação dele em C#, é só falar..
Abraço!
André Lima
Andre BOA TARDE MEU NOME É ROBSON TENHO DIFICUDADES EM UMA CRIAR UMA EXEMPLOR DE VALIDAÇÃO DE LICENÇA MENSAL EM UM PROGRAMA NA LINGUAGEM VB.NET ANDREA POR FAVOR VOÇE PODE MANDAR PARA MEU EMAIL UM EXEMPLO SIMPLES MEU EMAIL É ROBSON.C2016@Outlook.com por favor já fiz varias buscas na internet e nada aguardo?
Olá Robson, obrigado pelo comentário!
Como assim validação de licença mensal? Você poderia dar mais detalhes do que você está querendo implementar?
Abraço!
André Lima
Boa noite André,
Estou fazendo em C#.
Gostaria de uma ajuda, referente a imprimir o relatório com todo o conteúdo do DatagridView isso eu já estou conseguindo.
O que não estou conseguindo, é imprimir apenas a linha selecionada do DatagridView.
Ao selecionar a linha do DataGridView, deveria clicar em um botão para este abrir o formulário, e nele trazer apenas a linha que selecionei.
Se puder me dar uma ajuda agradeço!
Olá Priscila, obrigado pelo comentário!
Nesse caso, eu acredito que você poderia utilizar a propriedade “Selected” da linha do DataGridView para saber se a linha está selecionada ou não.. Aí você só adiciona a linha no DataSet / coleção se a propriedade Selected = true.. Utilizando a mesma lógica que eu apresentei no artigo, você poderia expandir o “if” que checa pelo IsNewRow, verificando também se a linha está selecionada:
Entendeu a ideia?
Abraço!
André Lima
Oi André obrigada por responder, mas sinceramente não entendi direito…
seguinte vou te explicar o que eu já tenho feito…
no form onde esta o DataGridView (que chama o form do relatorio):
RelatorioCadastro relcad = new RelatorioCadastro();
relcad.Show();
em uma classe separada tenho um método que trata referente ao DataSet:
public RelCadastro geraRelatorioRelatorioCadastro(String sql)
{
this.minhaconexao.Open();//abrindo conexao
SqlCommand cmd = new SqlCommand(sql, this.minhaconexao);//trata a sql apenas no command, a sql do consultar
ADP.SelectCommand = cmd;//obrigatoriamente tem que ser um sql command – guiar onde vai pegar o comand
ADP.Fill(this.DsRelCadastro, “cadastro”);//informar aqui tb o nome das tabelas que serão utilizadas, pois mostrará para onde tem que ir, pois a ponte esta feita
RelCadastro rptCadastro = new RelCadastro();//cria uma estância para poder abrir o relatório
rptCadastro.SetDataSource(DsRelCadastro);//coloca o relatório dentro do setdatasource
return rptCadastro;//retorna o relatório
}
e no form do relatório tenho (dessa forma ele já está mostrando todos os registros do form):
TrataBanco TB = new TrataBanco();
private void RelatorioCadastro_Load(object sender, EventArgs e)
{
RelCadastro rptCadastro = new RelCadastro();
String sql = “select * from cadastro”;
rptCadastro = TB.geraRelatorioRelatorioCadastro(sql);
crystalReportViewer1.ReportSource = rptCadastro;
}
Porém como falei só não entendi e não sei a forma de colocar para imprimir apenas a linha selecionada no form
Se puder me dar uma “luz” rs Agradeço…..
Olá Priscila!
Vamos lá.. A tabela de “Cadastro” tem uma chave, certo? Você vai precisar passar a chave do registro selecionado no DataGridView para o construtor do seu Form RelatorioCadastro.. O seu construtor recebendo a chave ficaria assim:
Depois, ao chamar o formulário do relatório, você teria que pegar o ID do cadastro na linha selecionada no DataGridView (exemplo disponível na documentação do MSDN aqui).. Esse ID você tem que passar para o construtor do formulário RelatorioCadastro:
Depois, finalmente, no formulário RelatorioCadastro, na hora que você monta a sentença que vai ser executada no banco, você tem que ajustar o seu SELECT para pegar somente os dados que tenham a chave recebida no construtor:
Acredito que algo parecido deva resolver o seu problema.. Conseguiu entender a ideia?
Abraço!
André Lima
Boa noite André, definitivamente não consegui, e sinceramente não sei no que estou errando…
se vc tiver um tempo e puder dar uma olhada nos códigos….
https://drive.google.com/drive/folders/0B7Z_K65A1kkvZDhlX0pNVm1ldEE?usp=sharing
FORM CADASTRO
CLASSE TRATA BANCO
FORM RELATORIO CADASTRO
Olá Priscila!
Aparentemente está quase tudo correto.. A única coisa que achei estranho é a hora que você está tentando pegar a linha selecionada.. Você está pegando o valor do método GetRowCount para preencher a variável chaveCadastro? Esse método só retorna a quantidade de linhas selecionadas, e não o ID que você tem que utilizar para filtrar o relatório..
Eu acredito que o código do clique do seu botão deveria ficar mais ou menos assim:
Tenta aí e depois avisa se funcionou.. E, se não funcionar, dá mais detalhes do que está acontecendo (por exemplo, se você está recebendo um erro ou se simplesmente o relatório está vindo em branco)..
Abraço!
André Lima
Olá André!
Este teu artigo foi o primeiro que me ajudou muito.
Teus artigos são fantasticos e muito objectivos, continua.
Muito obrigado
Abraço!
Olá Flávio, muito obrigado pelo comentário!
Fico feliz que este artigo tenha te ajudado.. Pode ficar tranquilo que eu vou continuar produzindo conteúdo aqui por um bom tempo.. :)
Abraço!
André Lima
Ok, André! o meu DataGrid é alimentando pelo Banco de Dados isso é conectei directamente, como ficaria para preencher o Relatório?
Olá Valdemar!
Se você está linkando o banco direto no relatório, você precisa passar as informações do banco antes de exibir o relatório.. Tem uns exemplos parecidos nesta thread de fórum:
How to programmatically set data source for Crystal Reports basic for VS 2008?
Mas, pelo que percebi pela sua resposta no outro artigo, você conseguiu fazer funcionar passando os dados via DataSet, correto? Essa é a forma mais indicada de passar os dados para o relatório mesmo (via DataSet)..
Abraço!
André Lima
Vc é o cara. valeu Andre Lima… Obrigador pelo tutorial.
Olá Alfredo, muito obrigado! Fico feliz por ter conseguido ajudar.. :)
Abraço!
André Lima
Saudações. É possível ao Sr. disponibilizar o código fonte (zipado) deste exemplo?
Obrigado!
Olá Will!
Se eu tivesse eu até passava, mas infelizmente eu não tenho.. Só tenho os projetos de artigos que foram escritos a partir de 2017.. Os mais antigos que isso eu não tenho..
Mas, é só seguir os passos do artigo que deve dar certinho.. Se ficar com alguma dúvida é só postar aqui..
Abraço!
André Lima
Olá André,
é possível desenhar o relatório sem precisar criar o DataSet ou o arquivo TTX?
Hoje eu tenho uma lista de objetos mas o Crystal não enxerga ela. Daí tenho que ficar populando um dataset na mão só para mandar para o Crystal.
Abraço!
Olá Jeison!
Uma outra opção seria trabalhar diretamente com as classes do seu projeto, como eu mostrei neste artigo sobre Crystal Reports com Entity Framework:
Gerando relatórios do Crystal Reports com Entity Framework
Abraço!
André Lima
Obrigado André, era isso que eu precisava.
Parabéns pelos artigos
De nada, Jeison.. Qualquer coisa estamos aí..
Abraço!
André Lima
Ola André, tudo bem?
Tenho lido algum de seus artigos para tentar sair de uma enrascada.
Estou desenvolvendo uma aplicação ERP web forms com banco MySql.
Tentei criar relatorios no crystal com classes objeto, mas quando vou add o database ao relatorio ele pede para selecionar um arquivo XML. Sabe me dizer o que acontece:
Olá Robson!
Não consegui entender direito o que está acontecendo no seu projeto.. Quando você diz “quando vou add o database ao relatorio ele pede para selecionar um arquivo XML”, o que exatamente você está querendo dizer? Poderia falar os passos que você seguiu para chegar nessa situação (quais opções você clicou, etc)?
Abraço!
André Lima
Bom dia André,
Quando adiciono o rpt ao projeto e vou setar o database em .NET objects, para criar o relatório a partir de uma classe, quando seleciono a mesma um form é aberto para informar os dados de conexão: caminho do arquivo e nome da classe. O esperado é um ADO.NET XML.
Pena que não consigo enviar um print.
Seria possível um contato por e-mail André? Se possível gostaria de efetuar uma consultoria e acertamos a sua hora técnica.
Grato pelo retorno.
Olá Robson!
Que coisa estranha hein.. Em qual lugar você está indo para setar a estrutura de dados do relatório? Você precisa ir em “Database Expert” e não em “Set Datasource Location”.. Veja como eu fiz neste outro artigo:
Gerando relatórios do Crystal Reports com Entity Framework
Quanto a contato via e-mail, o meu endereço é: contato [arroba] andrealveslima [ponto] com [ponto] br.. Infelizmente não estou trabalhando com consultoria no momento, mas podemos conversar sobre as suas dúvidas por e-mail sem problema nenhum..
Abraço!
André Lima