Juntando dois relatórios do Report Viewer em um só - André Alves de Lima
Juntando dois relatórios do Report Viewer em um só
andrealveslima | .NET, Relatórios, Report Viewer, Software Development, Visual Studio | 30
Ao desenvolvermos um aplicativo que contenha relatórios, muito provavelmente esse aplicativo não terá somente um, mas sim, múltiplos relatórios. Em algumas situações surge a necessidade de juntarmos dois ou mais relatórios em um só. O que fazemos nesse caso? Desenvolvemos um terceiro relatório com o layout dos dois relatórios originais ou será que existe uma alternativa menos trabalhosa? No artigo de hoje eu vou mostrar para você como juntar dois relatórios do Report Viewer em um só de uma maneira menos dolorosa.
Essa situação é mais comum do que podemos imaginar. Muitas vezes queremos imprimir duas listagens pré-existentes dentro de um mesmo relatório principal. Outras vezes, pode ser que tenhamos a necessidade de imprimirmos duas ou mais vias do mesmo relatório. E temos também o exemplo clássico do boleto bancário em que a primeira via é parecida com a segunda via, mas, não necessariamente idêntica.
Em todos esses casos nós poderíamos criar um outro relatório copiando o layout dos dois ou mais relatórios originais. Porém, isso daria um trabalho muito grande, além do trabalho em dobro que teríamos caso alguma coisa seja alterada no layout do relatório original.
Qual seria a alternativa para essa situação? A utilização de sub-relatórios! A ideia é criarmos um relatório separado que exibirá os relatórios "filhos" através da funcionalidade de sub-reports. Dessa forma, caso algo seja alterado nos relatórios originais, a alteração será automaticamente refletida no relatório "mestre".
Vamos ver como podemos resolver esse desafio com sub-relatórios? Primeiro, vamos criar dois relatórios que serão mesclados em um só.
Criando dois relatórios de exemplo
Poderíamos criar os mais diversos tipos de relatórios para demonstrarmos neste artigo. Porém, para não complicar muito as coisas, vamos desenhar rapidamente dois relatórios muito simples: um com uma listagem de clientes e outro com uma listagem de fornecedores. Não importa a complexidade dos relatórios que você quer juntar, a estratégia que vamos conferir neste artigo se aplica para qualquer tipo de relatório.
Primeiramente, vamos começar com um novo projeto do tipo "Windows Forms Application" e vamos criar duas classes bem simples que servirão de fonte de dados para os relatórios:
public class Fornecedor
public int FornecedorID { get; set; }
Public Property ClienteID As Integer
Public Property FornecedorID As Integer
Em seguida, compile o projeto. Caso não façamos a compilação do projeto neste ponto, existe a possibilidade do Report Viewer não reconhecer essas classes que acabamos de criar.
Executada a compilação do projeto, vamos adicionar dois relatórios do Report Viewer no nosso projeto: um chamado "RelatorioCliente" e outro chamado "RelatorioFornecedor". Ajuste o layout dos relatórios de forma que eles fiquem parecidos com as imagens abaixo:
Não vou detalhar neste artigo a criação desses dois relatórios, uma vez que eu já demonstrei a criação de inúmeros outros relatórios dos mais diversos tipos em outros artigos no passado. É só você dar uma olhada na categoria "Report Viewer" aqui do meu site que você vai encontrar diversos exemplos.
Criando o relatório "mestre"
Agora que já temos os dois relatórios originais, vamos ver como podemos juntá-los em um só utilizando sub-relatórios. A primeira coisa que temos que fazer é adicionarmos um terceiro relatório no projeto. Vamos chamar esse relatório de "RelatorioMestre". Dentro desse relatório, vamos adicionar dois controles do tipo "Subreport":
Feito isso, temos que abrir a propriedade de cada um dos controles "Subreport" para configurarmos o nome do relatório que deverá ser exibido em cada um dos sub-relatórios. Ele deverá sempre ser o nome do relatório sem a extensão "rdlc". Ou seja, no nosso caso, um dos sub-reports deverá conter o nome "RelatorioCliente" e o outro sub-report deverá conter o nome "RelatorioFornecedor":
Agora que já temos o nosso relatório "mestre" criado, vamos tentar exibi-lo. Para isso, vamos até o formulário, arrastamos um controle do tipo "ReportViewer" e configuramos para que ele aponte para o "RelatorioMestre.rdlc". Se executarmos o projeto neste momento, receberemos os seguintes erros:
Recebemos essa mensagem porque nós basicamente não alimentamos os relatórios de cliente e fornecedor. Para alimentarmos sub-relatórios no Report Viewer, nós temos que utilizar o evento SubreportProcessing do LocalReport. Vamos então criar um hook para esse evento no code-behind do formulário, dentro do construtor (ou no evento Load, caso você esteja trabalhando com VB.NET):
reportViewer1.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
void LocalReport_SubreportProcessing(object sender, Microsoft.Reporting.WinForms.SubreportProcessingEventArgs e)
AddHandler ReportViewer1.LocalReport.SubreportProcessing, AddressOf LocalReport_SubreportProcessing
Private Sub LocalReport_SubreportProcessing(Sender As Object, E As Microsoft.Reporting.WinForms.SubreportProcessingEventArgs)
Dentro da implementação do evento SubreportProcessing, temos que configurar a fonte de dados dos sub-relatórios. Como estamos mostrando dois relatórios completamente diferentes, as fontes de dados também serão diferentes. Para sabermos qual relatório está sendo carregado em cada chamada desse evento, nós temos que analisar o valor da propriedade "e.ReportPath". Essa propriedade trará o caminho do relatório sendo carregado, ou seja, no nosso caso o caminho conterá "RelatorioCliente" ou "RelatorioFornecedor":
if (e.ReportPath.Contains("RelatorioCliente"))
clientes.Add(new Cliente() { ClienteID = 1, Nome = "Cliente 1", Telefone = "Tel. Cliente 1" });
clientes.Add(new Cliente() { ClienteID = 2, Nome = "Cliente 2", Telefone = "Tel. Cliente 2" });
clientes.Add(new Cliente() { ClienteID = 3, Nome = "Cliente 3", Telefone = "Tel. Cliente 3" });
e.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetCliente", clientes));
else if (e.ReportPath.Contains("RelatorioFornecedor"))
fornecedores.Add(new Fornecedor() { FornecedorID = 1, Nome = "Fornecedor 1", Telefone = "Tel. Fornecedor 1" });
fornecedores.Add(new Fornecedor() { FornecedorID = 2, Nome = "Fornecedor 2", Telefone = "Tel. Fornecedor 2" });
fornecedores.Add(new Fornecedor() { FornecedorID = 3, Nome = "Fornecedor 3", Telefone = "Tel. Fornecedor 3" });
e.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetFornecedor", fornecedores));
If (E.ReportPath.Contains("RelatorioCliente")) Then
Dim Clientes = New List(Of Cliente)
Clientes.Add(New Cliente() With {.ClienteID = 1, .Nome = "Cliente 1", .Telefone = "Tel. Cliente 1"})
Clientes.Add(New Cliente() With {.ClienteID = 2, .Nome = "Cliente 2", .Telefone = "Tel. Cliente 2"})
Clientes.Add(New Cliente() With {.ClienteID = 3, .Nome = "Cliente 3", .Telefone = "Tel. Cliente 3"})
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetCliente", Clientes))
ElseIf (E.ReportPath.Contains("RelatorioFornecedor")) Then
Dim Fornecedores = New List(Of Fornecedor)
Fornecedores.Add(New Fornecedor() With {.FornecedorID = 1, .Nome = "Fornecedor 1", .Telefone = "Tel. Fornecedor 1"})
Fornecedores.Add(New Fornecedor() With {.FornecedorID = 2, .Nome = "Fornecedor 2", .Telefone = "Tel. Fornecedor 2"})
Fornecedores.Add(New Fornecedor() With {.FornecedorID = 3, .Nome = "Fornecedor 3", .Telefone = "Tel. Fornecedor 3"})
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetFornecedor", Fornecedores))
Atenção! Tome cuidado com o nome do DataSet! Ele deve ser exatamente o mesmo nome do DataSet utilizado nos relatórios "filhos" (por exemplo, nos relatórios utilizados acima, os nomes dos DataSets são "DataSetCliente" e "DataSetFornecedor"). Se o nome dos DataSets não bater (inclusive letras maiúsculas e minúsculas), você continuará recebendo o mesmo erro falando que o DataSet não foi especificado.
Vamos executar o projeto novamente para vermos o resultado:
OK. Os dois relatórios foram exibidos, mas, cadê o título que estava no cabeçalho dos relatórios filhos?
Ajustando o cabeçalho e rodapé dos relatórios filhos
Ao utilizarmos um relatório dentro de um "SubReport" do Report Viewer, somente a área de detalhes é exibida. Ou seja, tudo o que estiver no cabeçalho e rodapé de página será completamente ignorado. Para contornarmos essa limitação, se soubermos que um relatório será utilizado dentro de um controle "SubReport", temos que mover todo o conteúdo do cabeçalho para dentro da área de detalhes, mais especificamente, dentro da tabela.
Veja o antes:
Feito isso, temos que configurar essas linhas do topo da tabela para que elas repitam em todas as páginas. Conseguimos fazer isso ativando o modo avançado na janela de agrupamentos e configurando a propriedade "RepeatOnNewPage" como "true" em cada uma das seções estáticas do agrupamento de linhas:
Ainda não entendeu o que eu fiz ali em cima? Não tem problema, eu mostro para você um passo a passo neste gif animado:
Repita o mesmo processo para o segundo relatório e o resultado será este:
Quebra de páginas entre os sub-relatórios
Muito bem, até agora conseguimos mostrar os dois relatórios "filhos" dentro de um relatório "mestre", inclusive com os seus respectivos cabeçalhos. Porém, como é que podemos fazer para quebrar a página entre o relatório de clientes e o relatório de fornecedores? A maioria dos controles do Report Viewer possuem configurações de quebra de página, de forma que podemos configurá-los para quebrar página antes ou depois do controle. Entretanto, o controle "Subreport" não possui essa capacidade, então, como resolver isso?
Um pequeno ajuste (leia-se "gambiarra") que podemos fazer para termos uma quebra de página entre os sub-relatórios é coloca-los dentro de um controle "Rectangle". O controle "Rectangle" possui as propriedades de quebra de página, então, tudo o que estiver dentro dele respeitará essa configuração:
E com esse pequeno "ajuste", o relatório de clientes ficará na primeira página e o relatório de fornecedores ficará na segunda página. Legal, não?
Não é difícil exibirmos mais de um relatório "filho" dentro de um relatório "mestre" no Report Viewer. Com a funcionalidade de sub-relatórios essa tarefa acaba sendo até mesmo muito simples. Existem alguns pequenos "segredos" nesse processo, como o carregamento dos dados dos sub-relatórios, o ajuste do cabeçalho e rodapé dos relatórios "filhos" e a certa "gambiarra" que temos que fazer para adicionarmos quebra de páginas entre um sub-relatório e outro. Tudo isso você conferiu neste artigo de hoje.
E você, já precisou juntar dois relatórios do Report Viewer em um só? Se sim, você utilizou a metodologia que eu apresentei neste artigo ou você fez algo diferente? Se não, você já tinha parado para pensar que isso é possível? Pode ser que essa seja uma boa alternativa para alguns relatórios do seu sistema. De qualquer forma, não esqueça de dar uma passada ali na caixa de comentários para nos contar o que você achou do artigo e como foi a sua experiência com a utilização das técnicas apresentadas.
Image by Juhan Sonin used under Creative Commons
https://www.flickr.com/photos/juhansonin/5135576565
Revisão de meio de ano 2016 – como estão suas metas? Utilizando o Crystal Reports com MVVM no WPF
30 thoughts on "Juntando dois relatórios do Report Viewer em um só"
19 de agosto de 2016 às 17:44
Boa tarde, uma duvida sobre reports no visual studio. É possivel usar formulas de ProcV ?
A fórmula PROCV não existe no Report Viewer, porém, você pode utilizar a fórmula LOOKUP, que produz o mesmo resultado.. Se você tiver dois DataSets no seu relatório, você pode pegar dados do outro DataSet através dessa fórmula.. Tem um exemplo nesta thread do StackOverflow:
Lookup function in SSRS report
Jeferson Kal Lyns disse:
3 de fevereiro de 2017 às 15:32
André, li algumas de suas postagens sobre o ReportViewer e achei bastante legal a forma como trata tudo. Porém, estou com um problema, pode me ajudar?
No meu código, o SubreportProcessing não chega a ser iniciado, é como se passasse direto e daí sempre empaco no erro de "Data retrieval failed for the subreport". Pode me ajudar com isso?
6 de fevereiro de 2017 às 13:39
Olá Jeferson, obrigado pelo comentário!
Você está trabalhando com o Report Viewer para aplicações web ou desktop? Está utilizando alguma biblioteca extra ou somente o Report Viewer?
6 de fevereiro de 2017 às 19:20
André, obrigado por responder. Acabei resolvendo o problema com uma solução sua. O problema é que eu estava usando o ReportViewer for MVC e não sabia que o mesmo não suportava Subreports. Então. fui no Codeplex e li seu comentário e acabei resolvendo com a ajuda de um amigo. De qualquer forma, obrigado!
21 de fevereiro de 2017 às 15:22
Olá Jeferson, desculpa a demora, é que o seu comentário tinha caído no spam.. Enfim, fico feliz que tenha conseguido resolver no final das contas.. Qualquer coisa é só entrar em contato novamente.. :)
5 de abril de 2017 às 23:43
Olá André!! Saberia dizer se usando esta técnica é possível incluir um subreport que tenha layout dividido em colunas mas sem que ele perca as colunas? Estou tentando mas porém ele perde a formatação quando renderizado dentro do sub. Desde já agradeço pela atenção!
7 de abril de 2017 às 10:42
Infelizmente não.. :(
Que eu saiba, a funcionalidade de colunas não é suportada quando o relatório é exibido como sub-report.. A única saída que eu veria nesse caso para juntar os dois relatórios seria exportá-los diretamente para PDF e utilizar alguma biblioteca de manipulação de PDFs (como estas que eu mostrei neste outro artigo) para juntar os dois PDFs em um só..
Se você encontrar uma outra saída, avisa a gente.. :)
AndréMartinsSevero disse:
27 de abril de 2017 às 19:36
Olá André, tudo certo? Parabéns pelo site e pelos tutoriais, sua didática é muito boa!
Li esse tutorial e gostaria de saber se é possível que a partir de uma form com dois botões eu direcionar para os reports. Por exemplo, gerar o relatório numa tabela ou num gráfico.
André Severo.
28 de abril de 2017 às 8:37
Olá André, muito obrigado pelo comentário!
Deixa eu ver se eu entendi direito.. Você tem dois relatórios e você quer, a partir de um formulário com dois botões, um botão abre um relatório e outro botão abre outro relatório? Se for isso, acho que este outro artigo vai te ajudar:
Multi-relatórios no mesmo formulário com o Report Viewer
Agora, se não for isso, me explica com mais detalhes para eu conseguir entender melhor..
28 de abril de 2017 às 18:37
Isso mesmo. Vou dar uma lida e tentar implementar.
Muito obrigado André e parabéns pelo ótimo trabalho!
1 de maio de 2017 às 5:36
De nada, André! Qualquer dúvida na implementação é só entrar em contato..
29 de janeiro de 2018 às 17:13
Se eu for exportar o relatório para pdf, como uso o evento SubreportProcessing?
Olá Ely!
Não tem diferença se você for exibir o relatório no controle ou se você for exportar direto em PDF.. Você só precisa atachar o handler no evento SubreportProcessing da sua instância de LocalReport.. Exemplo:
var report = new Microsoft.Reporting.WinForms.LocalReport();
report.SubreportProcessing += SeuEventHandler; // Aqui você anexa o seu event handler...
report.ReportPath = "caminho.rdlc";
var dataSource = new Microsoft.Reporting.WinForms.ReportDataSource("NomeDataSet", dadosDoRelatorio);
report.DataSources.Add(dataSource);
var bytes = report.Render("PDF");
System.IO.File.WriteAllBytes("relatorio.pdf", bytes);
UILSON CLAUDIO FILHO disse:
1 de agosto de 2018 às 22:28
Boa tarde Andre
Estou seguindo seus passos no artigo, mas não entra no evento : reportViewer1.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
Nã estou entendendo o pq. Vc poderia me ajudar ?
23 de agosto de 2018 às 15:16
Olá Uilson!
Estranho hein.. Ele não dispara o evento ou não chega na linha onde o event handler é criado (linha onde tem o "+=")? Como é que ficou o seu código? Exatamente como foi demonstrado no artigo?
27 de setembro de 2018 às 15:20
Olá, André. Gostaria de saber como eu faço essa mesma quebra de página com as tabelas importadas do access e não criadas na classe como no exemplo mostrado, pois tentei fazer do mesmo jeito e não funcionou.
Não muda basicamente nada.. A única coisa é a parte de carregamento dos dados do banco, que você terá que fazer manualmente.. Você tem os dois relatórios separados funcionando corretamente? Se tiver, é só pegar o código que preenche cada um dos relatórios e aí você passa para o outro relatório que terá a mesclagem dos dois (com sub-relatórios, conforme apresentado no artigo)..
Enfim, o primeiro passo é você conseguir exibir os dois relatórios separadamente.. Uma vez que você tiver com isso pronto, a parte de juntar os dois relatórios em um só fica mais fácil..
3 de outubro de 2018 às 1:32
Olá, André. Quando você diz "código que preenche cada relatório" está se referindo ao
If (E.ReportPath.Contains("CJLista_Perecíveis")) Then
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("ListaDataSet", Lista_Perecíveis))
ElseIf (E.ReportPath.Contains("CJLista_Não_Perecíveis")) Then
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("ListaDataSet", Lista_Não_Perecíveis))
End Sub??
Porque o nome da tabela não reconhece, não to conseguindo exibir os dois relatórios no mesmo ReportView.
Sim, é isso mesmo.. Quando você diz "porque o nome da tabela não reconhece", o que você quer dizer exatamente? Você está recebendo um erro? Na hora de executar ou na hora de compilar?
12 de outubro de 2018 às 17:51
O erro que aparece em relação ao nome da tabela é esse "Lista_Perecíveis is not declared. It may be inaccessible due to its protection level"
Quando o nome da tabela não está entre aspas, agora que eu coloquei entre aspas e abro o form onde está o reportview aparece o erro da quinta foto do seu artigo.
15 de outubro de 2018 às 13:25
Poste o código todo do seu formulário, por favor.. Ao colocar "Lista_Pereciveis" em aspas, você está na verdade passando uma string com esse conteúdo para o relatório, o que não faz o menor sentido.. Você precisa, na verdade, declarar a variável "Lista_Perecíveis" no nível do formulário, de forma que ela fique acessível no método LocalReport_SubreportProcessing, justamente como eu mostrei no artigo..
24 de outubro de 2018 às 15:13
Public Class CJlistafinal
Private Sub CJlistafinal_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'ListaDataSet.Lista_Perecíveis' table. You can move, or remove it, as needed.
Me.Lista_PerecíveisTableAdapter.Fill(Me.ListaDataSet.Lista_Perecíveis)
25 de outubro de 2018 às 13:26
Ao invés de somente colocar "Lista_Perecíveis" e "Lista_Não_Perecíveis", você já tentou colocar o nome do DataSet junto? Por exemplo:
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("ListaDataSet", Me.ListaDataSet.Lista_Perecíveis))
25 de outubro de 2018 às 13:37
Então André acabei de testar e mesmo assim continua com erro
Qual é o erro agora? Continua a mesma mensagem de antes? Ou a mensagem de erro mudou?
25 de outubro de 2018 às 13:49
Agora o erro mudou para este:
"Overload resolution failed because no accessible 'New' is most specific for these arguments:
'Public Sub (name As String, dataSourceValue As System.Collection.IEnumerable)': Not most specific.
'Public Sub (name As String, dataSourceValue As System.Data.DataTable)': Not most specific."
Tente utilizar um DirectCast para fazer uma conversão em DataTable.. Exemplo:
E.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("ListaDataSet", DirectCast(Me.ListaDataSet.Lista_Perecíveis, DataTable)))
31 de outubro de 2018 às 14:20
coloquei esse código e na visualização aparece a mesma mensagem da foto 5 do seu artigo
Então é porque ou:
1) O nome do DataSet não está batendo com o que está definido no relatório
2) O código do evento "SubReportprocessing" não está sendo disparado
Confira o nome do DataSet nos relatórios e veja se ele realmente se chama "ListaDataSet".. Caso isso esteja correto, coloque um breakpoint no método "LocalReport_SubreportProcessing" e veja se ele está realmente sendo chamado e se está entrando dentro do "If" corretamente..