André Alves de Lima

Talking about Software Development and more…

Juntando dois relatórios do Report Viewer em um só

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:

    // C#
    public class Cliente
    {
        public int ClienteID { get; set; }
        public string Nome { get; set; }
        public string Telefone { get; set; }
    }
    public class Fornecedor
    {
        public int FornecedorID { get; set; }
        public string Nome { get; set; }
        public string Telefone { get; set; }
    }
' VB.NET
Public Class Cliente
    Public Property ClienteID As Integer
    Public Property Nome As String
    Public Property Telefone As String
End Class
Public Class Fornecedor
    Public Property FornecedorID As Integer
    Public Property Nome As String
    Public Property Telefone As String
End Class

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):

        // C#
        public Form1()
        {
            InitializeComponent();
            reportViewer1.LocalReport.SubreportProcessing += LocalReport_SubreportProcessing;
        }

        void LocalReport_SubreportProcessing(object sender, Microsoft.Reporting.WinForms.SubreportProcessingEventArgs e)
        {
            
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        AddHandler ReportViewer1.LocalReport.SubreportProcessing, AddressOf LocalReport_SubreportProcessing

        Me.ReportViewer1.RefreshReport()
    End Sub

    Private Sub LocalReport_SubreportProcessing(Sender As Object, E As Microsoft.Reporting.WinForms.SubreportProcessingEventArgs)

    End Sub

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“:

        // C#
        void LocalReport_SubreportProcessing(object sender, Microsoft.Reporting.WinForms.SubreportProcessingEventArgs e)
        {
            if (e.ReportPath.Contains("RelatorioCliente"))
            {
                var clientes = new List<Cliente>();
                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"))
            {
                var fornecedores = new List<Fornecedor>();
                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));
            }
        }
    ' VB.NET
    Private Sub LocalReport_SubreportProcessing(Sender As Object, E As Microsoft.Reporting.WinForms.SubreportProcessingEventArgs)
        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))
        End If
    End Sub

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:

E o depois:

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?

Concluindo

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.

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 Juhan Sonin used under Creative Commons
https://www.flickr.com/photos/juhansonin/5135576565

Newsletter do André Lima

* indicates required



Powered by MailChimp

30 thoughts on “Juntando dois relatórios do Report Viewer em um só

  • Rodrigo disse:

    Boa tarde, uma duvida sobre reports no visual studio. É possivel usar formulas de ProcV ?

    • andrealveslima disse:

      Olá Rodrigo!

      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

      Abraço!
      André Lima

  • Jeferson Kal Lyns disse:

    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?

    • andrealveslima disse:

      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?

      Abraço!
      André Lima

    • Jeferson Kal Lyns disse:

      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!

      • andrealveslima disse:

        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.. :)

        Abraço!
        André Lima

  • Fabio Muniz disse:

    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!

    • andrealveslima disse:

      Olá Fabio!

      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.. :)

      Abraço!
      André Lima

  • AndréMartinsSevero disse:

    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.

    Obrigado!

    André Severo.

    • andrealveslima disse:

      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..

      Abraço!
      André Lima

  • Ely disse:

    Olá, André!
    Gostei muito do artigo.
    Se eu for exportar o relatório para pdf, como uso o evento SubreportProcessing?

    • andrealveslima disse:

      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);
       
          report.Refresh();
          var bytes = report.Render("PDF");
          System.IO.File.WriteAllBytes("relatorio.pdf", bytes);
      

      Abraço!
      André Lima

  • UILSON CLAUDIO FILHO disse:

    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 ?

    Obrigado

    • andrealveslima disse:

      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?

      Abraço!
      André Lima

  • Lara disse:

    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.

    • andrealveslima disse:

      Olá Lara!

      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..

      Abraço!
      André Lima

      • Lara disse:

        Olá, André. Quando você diz “código que preenche cada relatório” está se referindo ao
        Private Sub LocalReport_SubreportProcessing(Sender As Object, E As Microsoft.Reporting.WinForms.SubreportProcessingEventArgs)
        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 If
        End Sub??
        Porque o nome da tabela não reconhece, não to conseguindo exibir os dois relatórios no mesmo ReportView.

        • andrealveslima disse:

          Olá Lara!

          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?

          Abraço!
          André Lima

  • Lara disse:

    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.

    • andrealveslima disse:

      Olá Lara!

      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..

      Abraço!
      André Lima

      • Lara disse:

        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)

        AddHandler ReportViewer1.LocalReport.SubreportProcessing, AddressOf LocalReport_SubreportProcessing

        Me.ReportViewer1.RefreshReport()
        End Sub

        Private Sub LocalReport_SubreportProcessing(Sender As Object, E As Microsoft.Reporting.WinForms.SubreportProcessingEventArgs)
        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 If
        End Sub

        End Class

        • andrealveslima disse:

          Olá Lara!

          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))
          

          Abraço!
          André Lima

  • Lara disse:

    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.”

    • andrealveslima disse:

      Olá Lara!

      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)))
      

      Abraço!
      André Lima

      • Lara disse:

        coloquei esse código e na visualização aparece a mesma mensagem da foto 5 do seu artigo

        • andrealveslima disse:

          Olá Lara!

          Então é porque ou:

          1) O nome do DataSet não está batendo com o que está definido no relatório
          ou
          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..

          Abraço!
          André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *