Quantcast
Channel: Relatórios – André Alves de Lima
Viewing all 61 articles
Browse latest View live

Trabalhando com gráficos no Report Viewer

$
0
0

O sucesso de um sistema depende, muitas vezes, da aprovação do nível gerencial da empresa que vai utilizá-lo. Nesse nível da hierarquia empresarial, os relatórios e ferramentas de análise disponíveis no aplicativo fazem uma grande diferença. E sabe o que vai impressionar mais ainda os gerentes e diretores que possivelmente farão uso da aplicação? Gráficos. Muitos gráficos.

Não existe uma maneira mais fácil de interpretarmos dados estatísticos do que através da utilização de gráficos. Se você não está levando em consideração essa funcionalidade nos seus relatórios, você está deixando na mesa uma grande porcentagem da aprovação do seu sistema. Para te ajudar nessa empreitada, no artigo de hoje eu mostrarei como é fácil trabalharmos com gráficos no Report Viewer.

Criando o projeto de exemplo

Para demonstrar a utilização de gráficos no Report Viewer, vamos supor que você tenha um aplicativo que gerencia vendas. Nessa aplicação, em um dos módulos, você armazena as vendas feitas de cada produto, por cada vendedor, em cada área geográfica.

Vamos começar criando um projeto do tipo “Windows Forms Application“. Poderíamos criar qualquer tipo de projeto, uma vez que o Report Viewer funciona também no WPF e em aplicações web. Porém, como é mais fácil explicar com o Windows Forms, (além dessa plataforma já estar extremamente estável – diferente do MVC, por exemplo, que traz grandes mudanças a cada nova versão), escolhi demonstrar esse relatório dessa forma.

Feito isso, a fim de simplificar o nosso exemplo, vamos criar uma classe especial que trará as informações de cada venda em um só lugar (comprador, vendedor, região, produto, data e valor da venda). Chamaremos essa classe de “DadosRelatorioVenda“:

    // C#
    public class DadosRelatorioVenda
    {
        public string NomeComprador { get; set; }
        public string NomeVendedor { get; set; }
        public string RegiaoVenda { get; set; }
        public string CategoriaProduto { get; set; }
        public DateTime DataVenda { get; set; }
        public double ValorVenda { get; set; }
    }
' VB.NET
Public Class DadosRelatorioVenda
    Public Property NomeComprador As String
    Public Property NomeVendedor As String
    Public Property RegiaoVenda As String
    Public Property CategoriaProduto As String
    Public Property DataVenda As DateTime
    Public Property ValorVenda As Double
End Class

Logo após a criação dessa classe, compile o projeto. Caso esqueçamos desse passo, a engine do Report Viewer não detectará a sua existência e, por consequência, não conseguiremos criar um novo relatório com ela.

Criando o relatório com gráficos

Agora que já temos a classe que servirá de fonte de dados para o nosso relatório, podemos prosseguir com a sua criação. Para isso, adicione um novo item do tipo “Report” no projeto, dando o nome de “RelatorioVenda“:

Com o relatório adicionado, clique com o botão direito na “área externa” do relatório e escolha a opção para adicionarmos um cabeçalho no relatório:

Dentro do cabeçalho, vamos adicionar um título para o relatório – “Dashboard de Vendas“:

Adicionado o cabeçalho, vamos agora adicionar uma fonte de dados para o relatório. Para isso, na janela “Report Data“, clique com o botão direito sobre “Datasets” e escolha a opção “Add Dataset“:

Na janela que se abre, escolha a opção “Object” para o tipo de fonte de dados do Dataset:

Finalmente, encontre a classe “DadosRelatorioVenda” na lista de classes do projeto e finalize o assistente:

Feito isso, chegou a hora de começarmos a criar os nossos gráficos. Para adicionarmos um novo gráfico nos nossos relatórios do Report Viewer, a primeira opção é arrastarmos esse tipo de controle da caixa de ferramentas para dentro do nosso relatório. Já a segunda opção é clicarmos com o botão direito sobre o relatório e escolhermos a opção “Insert => Chart“:

Independentemente da opção que escolhermos, ao adicionarmos um gráfico no Report Viewer, o Visual Studio nos perguntará qual o tipo de gráfico queremos criar. Veja que podemos criar inúmeros tipos de gráficos com o Report Viewer:

Vamos começar com um gráfico de pizza 3d. Para configurarmos os gráficos no Report Viewer, temos que clicar sobre eles e escolhermos as opções na smart tag “Chart Data“. Podemos configurar esse gráfico de pizza para mostrarmos, por exemplo, o total de vendas por região, escolhendo o somatório de “ValorVenda” na parte de “Valores” e a coluna “RegiaoVenda” na parte de “Grupos de Categorias“:

O título do relatório pode ser alterado clicando duas vezes sobre ele. Altere o título para “Vendas por Região“:

Algo muito comum em gráficos de pizza é a exibição de rótulos para as fatias do gráfico. Os rótulos podem ser facilmente adicionados clicando com o botão direito sobre uma das fatias do gráfico e escolhendo “Show Data Labels“:

Por padrão, o Report Viewer mostra o valor da série como rótulo (ou seja, no nosso caso, o somatório de vendas de cada região). Para alterar o valor que será exibido nas fatias, clique com o botão direito sobre um dos rótulos e escolha a opção “Series Label Properties“:

Para mostrarmos a porcentagem de vendas de cada região, podemos escolher a opção “#PERCENT“:

Ao fazermos isso, o Report Viewer perguntará se queremos alterar a propriedade “UseValueAsLabel” para false, uma vez que só é possível exibirmos a porcentagem caso essa propriedade esteja configurada como false. Dessa forma, quando o Report Viewer fizer essa pergunta, confirme o diálogo:

Feito isso, que tal deixarmos o nosso relatório mais rico adicionando mais alguns gráficos? Adicione um gráfico do tipo “funil 3D” para exibirmos as “Top categorias de produtos“:

Depois, um gráfico de linhas com marcadores para mostrarmos as “Vendas por dia“:

E, finalmente, um gráfico do tipo “colunas 3D empilhadas” para mostrarmos as vendas por Vendedores e Produtos:

Veja o layout final do relatório com todos os gráficos adicionados:

Exibindo o relatório

OK, agora que já temos o relatório com os gráficos, como é que exibimos esse relatório? Simples! Vamos até o nosso formulário, abrimos a caixa de ferramentas e arrastamos um controle do tipo “Report Viewer” para dentro do formulário. Na smart tag do controle, escolhermos o nosso relatório e “dockamos” o controle no formulário:

Em teoria, ao fazermos isso, o relatório está pronto para ser exibido. Porém, como não temos nenhum dado de vendas criado até o momento, os gráficos aparecerão vazios. Dessa forma, nessa etapa, precisamos adicionar os dados de vendas para que o relatório possua algum valor.

Normalmente, nesse momento nós carregaríamos os dados das vendas do nosso banco de dados. Porém, como não temos dados “de verdade“, vamos criar alguns dados de exemplo aleatórios. Para isso, adicione o método “GerarExemplo” na classe “DadosRelatorioVenda“:

    // C#
    public class DadosRelatorioVenda
    {
        public string NomeComprador { get; set; }
        public string NomeVendedor { get; set; }
        public string RegiaoVenda { get; set; }
        public string CategoriaProduto { get; set; }
        public DateTime DataVenda { get; set; }
        public double ValorVenda { get; set; }

        private static Random _rand = new Random();

        public static DadosRelatorioVenda GerarExemplo()
        {
            var compradores = new string[] { "Lojinha do Zé", "Mercearia da Esquina", "Tabacaria Top" };
            var vendedores = new string[] { "Fulaninho de Tal", "Beltrano Vende Tudo", "João do Caminhão" };
            var regioes = new string[] { "Norte", "Sul", "Leste", "Oeste", "Centro" };
            var categorias = new string[] { "Doce", "Salgado", "Perecível", "Bebida" };

            return new DadosRelatorioVenda()
            {
                NomeComprador = compradores[_rand.Next(3)],
                NomeVendedor = vendedores[_rand.Next(3)],
                RegiaoVenda = regioes[_rand.Next(5)],
                CategoriaProduto = categorias[_rand.Next(4)],
                DataVenda = DateTime.Now.AddDays(_rand.Next(30)),
                ValorVenda = _rand.Next(1000)
            };
        }
    }
' VB.NET
Public Class DadosRelatorioVenda
    Public Property NomeComprador As String
    Public Property NomeVendedor As String
    Public Property RegiaoVenda As String
    Public Property CategoriaProduto As String
    Public Property DataVenda As DateTime
    Public Property ValorVenda As Double

    Private Shared Rand As New Random()

    Public Shared Function GerarExemplo() As DadosRelatorioVenda
        Dim Compradores = New String() {"Lojinha do Zé", "Mercearia da Esquina", "Tabacaria Top"}
        Dim Vendedores = New String() {"Fulaninho de Tal", "Beltrano Vende Tudo", "João do Caminhão"}
        Dim Regioes = New String() {"Norte", "Sul", "Leste", "Oeste", "Centro"}
        Dim Categorias = New String() {"Doce", "Salgado", "Perecível", "Bebida"}

        Return New DadosRelatorioVenda() With { _
            .NomeComprador = Compradores(Rand.[Next](3)), _
            .NomeVendedor = Vendedores(Rand.[Next](3)), _
            .RegiaoVenda = Regioes(Rand.[Next](5)), _
            .CategoriaProduto = Categorias(Rand.[Next](4)), _
            .DataVenda = DateTime.Now.AddDays(Rand.[Next](30)), _
            .ValorVenda = Rand.[Next](1000) _
        }
    End Function
End Class

Por fim, vamos até o code-behind do nosso formulário e vamos fazer um “for” de zero até trinta adicionando dados de exemplo no nosso BindingSource:

        // C#
        private void Form1_Load(object sender, EventArgs e)
        {
            for (int cont = 0; cont <= 30; cont++)
            {
                DadosRelatorioVendaBindingSource.Add(DadosRelatorioVenda.GerarExemplo());
            }

            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        For cont As Integer = 0 To 30
            DadosRelatorioVendaBindingSource.Add(DadosRelatorioVenda.GerarExemplo())
        Next

        Me.ReportViewer1.RefreshReport()
    End Sub

Pronto! Execute o projeto e veja o resultado:

Concluindo

Um dos artifícios para deixarmos os nossos relatórios mais atraentes é a utilização de gráficos. Nesse artigo você conferiu como é fácil acrescentar vários tipos de gráficos nos relatórios do Report Viewer. Agora que você aprendeu como fazer, não perca essa oportunidade de impressionar os seus usuários. Ah, e depois volte aqui nos comentários e conte para gente quais foram os tipos de gráficos que você adicionou nos seus relatórios.

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. 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/graph-pie-chart-business-finance-963016/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com gráficos no Report Viewer appeared first on André Alves de Lima.


Trabalhando com o Report Viewer no WPF

$
0
0

Uma das perguntas relacionadas ao Report Viewer que eu já recebi múltiplas vezes é: “André, como é que eu faço para utilizar o Report Viewer no WPF?“. A resposta não é complicada e eu normalmente encaminho os(as) leitores(as) para algum artigo em inglês que mostre essa implementação. Mas, e se a pessoa não consegue entender esse artigo? Ou, pior ainda, e se a pessoa não domina muito bem o inglês?

Foi pensando nisso que eu resolvi escrever esse tutorial para mostrar para todos vocês, de uma vez por todas, como podemos utilizar o Report Viewer no WPF.

Criando o relatório

Antes de tudo, a primeira coisa que temos que fazer é criarmos um novo relatório. Uma vez que o foco desse artigo não é o relatório em si, mas sim, a sua exibição no WPF, criaremos um relatório muito simples, que fará apenas a listagem de funcionários. Vamos começar criando um novo projeto do tipo “WPF Application” e, dentro desse projeto, vamos adicionar uma classe chamada “DadosRelatorioFuncionario“:

    // C#
    public class DadosRelatorioFuncionario
    {
        public string Nome { get; set; }
        public string Sobrenome { get; set; }
        public string Cargo { get; set; }
    }
' VB.NET
Public Class DadosRelatorioFuncionario
    Public Property Nome As String
    Public Property Sobrenome As String
    Public Property Cargo As String
End Class

Feito isso, compile o projeto. Caso não compilemos o projeto nesse ponto, o Report Viewer não detectará a existência dessa nova classe e não conseguiremos utiliza-la no nosso relatório.

Compilado o projeto, vamos adicionar um novo item do tipo “Report“, disponível dentro da categoria “Reporting“. Dê o nome de “RelatorioFuncionario” para esse novo item:

Dentro do relatório, vamos agora até a janela “Report Data” para criarmos um novo DataSet, clicando com o botão direito sobre “Datasets” e escolhendo a opção “Add Dataset“:

Na janela de escolha do tipo de fonte de dados, escolha o tipo “Object“, uma vez que iremos utilizar a classe “DadosRelatorioFuncionario” para alimentar o nosso relatório:

Na próxima janela, encontre a classe “DadosRelatorioFuncionario“, marque o CheckBox correspondente e finalize o assistente:

Por fim, escolha o nome “DataSetRelatorio” para o DataSet e clique em “OK“. Guarde esse nome, pois ele é muito importante, uma vez que precisaremos dele quando estivermos exibindo o relatório:

Agora que já temos o DataSet no relatório, vamos adicionar uma tabela para listarmos os funcionários. Arraste os campos do DataSet para dentro dessa tabela e ajuste o layout de forma que o relatório fique parecido com a imagem abaixo:

Exibindo o relatório no WPF

Com o relatório criado, chegou a hora de exibirmos esse relatório na nossa Window do WPF. Primeiramente, temos que adicionar a referência ao Report Viewer do Windows Forms, já que o WPF não possui suporte nativo ao Report Viewer e só é possível exibi-lo dentro de um WindowsFormsHost:

Feito isso, vamos até a nossa Window e, no seu “cabeçalho“, vamos adicionar uma referência ao namespace do Report Viewer:

xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"

Em seguida, vá até a caixa de ferramentas e arraste um controle do tipo WindowsFormsHost para dentro do Grid e, dentro desse WindowsFormsHost, adicione um controle do ReportViewer, dando o nome de “ReportViewer” e já declarando um handler para o evento “Load“, onde faremos o carregamento do relatório:

    <Grid>
        <WindowsFormsHost>
            <rv:ReportViewer x:Name="ReportViewer" Load="ReportViewer_Load"/>
        </WindowsFormsHost>
    </Grid>

Agora a única coisa que está faltando é fazer o carregamento do relatório no code-behind da nossa Window. No método “ReportViewer_Load“, primeiramente criaremos uma lista de “DadosRelatorioFuncionario” contendo alguns dados de exemplo. Logo em seguida, criamos um “ReportDataSource“, adicionamos essa fonte de dados no relatório, configuramos o caminho do embedded resource rdlc e chamamos um “RefreshReport” para carregarmos o relatório:

        // C#
        private void ReportViewer_Load(object sender, EventArgs e)
        {
            var dadosRelatorio = new List<DadosRelatorioFuncionario>();
            dadosRelatorio.Add(new DadosRelatorioFuncionario() { Nome = "André", Sobrenome = "Alves de Lima", Cargo = "Programador" });
            dadosRelatorio.Add(new DadosRelatorioFuncionario() { Nome = "Fulano", Sobrenome = "da Silva", Cargo = "Gerente" });
            dadosRelatorio.Add(new DadosRelatorioFuncionario() { Nome = "José", Sobrenome = "da Esquina", Cargo = "Analista" });
            dadosRelatorio.Add(new DadosRelatorioFuncionario() { Nome = "Maria", Sobrenome = "Souza", Cargo = "Analista" });

            var dataSource = new Microsoft.Reporting.WinForms.ReportDataSource("DataSetRelatorio", dadosRelatorio);
            ReportViewer.LocalReport.DataSources.Add(dataSource);
            ReportViewer.LocalReport.ReportEmbeddedResource = "ReportViewerWPF.RelatorioFuncionario.rdlc";

            ReportViewer.RefreshReport();
        }
    ' VB.NET
    Private Sub ReportViewer_Load(sender As Object, e As EventArgs)
        Dim DadosRelatorio As New List(Of DadosRelatorioFuncionario)
        DadosRelatorio.Add(New DadosRelatorioFuncionario() With {.Nome = "André", .Sobrenome = "Alves de Lima", .Cargo = "Programador"})
        DadosRelatorio.Add(New DadosRelatorioFuncionario() With {.Nome = "Fulano", .Sobrenome = "da Silva", .Cargo = "Gerente"})
        DadosRelatorio.Add(New DadosRelatorioFuncionario() With {.Nome = "José", .Sobrenome = "da Esquina", .Cargo = "Analista"})
        DadosRelatorio.Add(New DadosRelatorioFuncionario() With {.Nome = "Maria", .Sobrenome = "Souza", .Cargo = "Analista"})

        Dim DataSource As New Microsoft.Reporting.WinForms.ReportDataSource("DataSetRelatorio", DadosRelatorio)
        ReportViewer.LocalReport.DataSources.Add(DataSource)
        ReportViewer.LocalReport.ReportEmbeddedResource = "ReportViewerWPF.RelatorioFuncionario.rdlc"

        ReportViewer.RefreshReport()
    End Sub

Pronto! Execute o projeto e veja o relatório sendo carregado com sucesso dentro do WindowsFormsHost:

Concluindo

Apesar de não termos um controle específico do Report Viewer para WPF, conseguimos utilizar o controle do Windows Forms sem problema algum. Para isso, basta adicionarmos um WindowsFormsHost na nossa Window do WPF e, dentro dele, adicionamos o controle do Report Viewer do Windows Forms. Neste artigo, você conferiu quais são os passos que você deve seguir para exibir os seus relatórios do Report Viewer no WPF.

Agora conte para gente nos comentários: você trabalha com o WPF ou com o Windows Forms na sua aplicação de negócios? Caso você utilize o WPF, você já tinha pensado em exibir relatórios do Report Viewer na sua aplicação? Achou que era impossível por não existir um controle nativo do WPF? Estou curioso para saber essas informações!

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/document-agreement-documents-sign-428331/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com o Report Viewer no WPF appeared first on André Alves de Lima.

Como podemos exportar relatórios do Report Viewer?

$
0
0

Uma necessidade muito comum quando trabalhamos com relatórios nos nossos sistemas é a questão da exportação dos relatórios. A exibição dos relatórios é somente uma parte do processo de geração de relatórios. Muitas vezes o usuário precisa de uma versão em DOC ou PDF para arquivar ou até mesmo para mandar por e-mail.

Como toda ferramenta de relatórios que se preze, o Report Viewer possui a opção de exportação para alguns formatos bem conhecidos: Word, Excel, PDF e imagem. Neste artigo eu vou mostrar para você como exportar relatórios do Report Viewer diretamente no controle visualizador, como desabilitar formatos de exportação específicos, como habilitar alguns formatos escondidos por padrão e, por fim, como exportar os relatórios sem utilizar o preview. Vamos lá?

Criando o projeto de exemplo

Para começarmos esse artigo, vamos criar um relatório extremamente simples, que será a base para todos os exemplos de exportação. Como o foco do artigo não é a geração do relatório em si, não vou perder muito tempo com essa explicação, OK?

Nota: se você quiser conferir, eu já escrevi diversos outros artigos onde eu mostro mais detalhes sobre a geração de relatórios como, por exemplo, este aqui onde eu explico como criar um relatório mestre/detalhe com Report Viewer + Entity Framework. Ah, e claro, além disso tem o meu livro sobre Report Viewer também, caso você queira se aprofundar nesse tema.

Enfim, para o exemplo básico deste artigo, vamos criar um projeto do tipo “Windows Forms Application“. Nesse projeto, vamos adicionar uma classe “Funcionario“, que servirá de fonte de dados para o nosso relatório de exemplo:

    // C#
    public class Funcionario
    {
        public string Nome { get; set; }
        public string Sobrenome { get; set; }
        public DateTime DataNascimento { get; set; }
        public string Cargo { get; set; }
        public DateTime DataAdmissao { get; set; }
        public DateTime DataDemissao { get; set; }
    }
' VB.NET
Public Class Funcionario
    Public Property Nome As String
    Public Property Sobrenome As String
    Public Property DataNascimento As DateTime
    Public Property Cargo As String
    Public Property DataAdmissao As DateTime
    Public Property DataDemissao As DateTime
End Class

Antes de continuarmos, compile o projeto. Caso contrário, a engine do Report Viewer não reconhecerá essa nova classe na hora de criarmos o DataSet do relatório. Feito isso, vamos adicionar um relatório básico, que listará os funcionários. Dê o nome de “RelatorioFuncionarios” para o novo relatório e adicione um DataSet (do tipo “Object DataSource“, escolhendo a classe “Funcionario” que criamos anteriormente) e dê o nome de “DataSetFuncionario“. No corpo do relatório, adicione uma Table a arraste os campos do DataSet para dentro dessa tabela:

Em seguida, vamos voltar ao formulário para adicionarmos o controle do Report Viewer. Configure o controle de forma que ele aponte para o relatório que acabamos de criar:

No code-behind, dentro do evento “Load“, vamos criar alguns funcionários que deverão ser listados no relatório:

        // C#
        private void FormRelatorio_Load(object sender, EventArgs e)
        {
            FuncionarioBindingSource.Add(new Funcionario() { Nome = "André", Sobrenome = "Lima", DataNascimento = new DateTime(1984, 1, 1), Cargo = "Programador", DataAdmissao = new DateTime(2008, 2, 1) });
            FuncionarioBindingSource.Add(new Funcionario() { Nome = "Fulano", Sobrenome = "de Tal", DataNascimento = new DateTime(1973, 4, 15), Cargo = "Gerente", DataAdmissao = new DateTime(1998, 4, 1) });
            FuncionarioBindingSource.Add(new Funcionario() { Nome = "Beltrano", Sobrenome = "Silva", DataNascimento = new DateTime(1959, 2, 23), Cargo = "Diretor", DataAdmissao = new DateTime(1993, 1, 1) });
            FuncionarioBindingSource.Add(new Funcionario() { Nome = "João", Sobrenome = "Souza", DataNascimento = new DateTime(1989, 8, 14), Cargo = "Estagiário", DataAdmissao = new DateTime(2010, 6, 1), DataDemissao = new DateTime(2012, 12, 31) });
            this.reportViewer.RefreshReport();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        FuncionarioBindingSource.Add(New Funcionario() With {.Nome = "André", .Sobrenome = "Lima", .DataNascimento = New DateTime(1984, 1, 1), .Cargo = "Programador", .DataAdmissao = New DateTime(2008, 2, 1)})
        FuncionarioBindingSource.Add(New Funcionario() With {.Nome = "Fulano", .Sobrenome = "de Tal", .DataNascimento = New DateTime(1973, 4, 15), .Cargo = "Gerente", .DataAdmissao = New DateTime(1998, 4, 1)})
        FuncionarioBindingSource.Add(New Funcionario() With {.Nome = "Beltrano", .Sobrenome = "Silva", .DataNascimento = New DateTime(1959, 2, 23), .Cargo = "Diretor", .DataAdmissao = New DateTime(1993, 1, 1)})
        FuncionarioBindingSource.Add(New Funcionario() With {.Nome = "João", .Sobrenome = "Souza", .DataNascimento = New DateTime(1989, 8, 14), .Cargo = "Estagiário", .DataAdmissao = New DateTime(2010, 6, 1), .DataDemissao = New DateTime(2012, 12, 31)})
        Me.ReportViewer.RefreshReport()
    End Sub

Pronto! Execute o projeto e veja o resultado:

Desabilitando a exportação

Agora que já temos o nosso relatório de exemplo sendo exibido, vamos começar a trabalhar com a exportação. A primeira coisa que temos que aprender é como habilitar ou desabilitar a exportação por completo. Isso pode ser feito de maneira muito fácil através da propriedade “ShowExportButton” (no controle desktop) ou “ShowExportControls” (no controle web):

Se deixarmos essa propriedade como “true” (que é o padrão), as opções de exportação serão exibidas na barra de tarefas do controle:

Por outro lado, ao configurarmos essa propriedade para “false“, o botão de exportação não será exibido:

Desabilitando alguns formatos de exportação

OK, agora que já vimos como desabilitar as opções de exportação por completo, vamos ver como conseguimos desabilitar somente alguns formatos de exportação? A Fernanda Sallai já escreveu um artigo especialmente sobre isso uns tempos atrás, portanto, não vou me aprofundar muito sobre o tema.

Basicamente, para desabilitarmos somente alguns formatos de exportação, temos que recuperar os formatos utilizando o método “ListRenderingExtensions” e, via reflection, alteramos o atributo “m_isVisible” para false nos formatos que desejarmos. Por exemplo, para desabilitarmos a exportação para o Excel, podemos utilizar esse trecho de código no evento “Load” do controle do Report Viewer:

        // C#
        private void reportViewer_Load(object sender, EventArgs e)
        {
            var fieldInfo = typeof(Microsoft.Reporting.WinForms.RenderingExtension).GetField("m_isVisible", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            
            foreach (var extension in this.reportViewer.LocalReport.ListRenderingExtensions())
            {
                if (string.Compare("EXCELOPENXML", extension.Name) == 0)
                    fieldInfo.SetValue(extension, false);
            }
        }
    ' VB.NET
    Private Sub ReportViewer_Load(sender As Object, e As EventArgs) Handles ReportViewer.Load
        Dim fieldInfo As System.Reflection.FieldInfo = GetType(Microsoft.Reporting.WinForms.RenderingExtension).GetField("m_isVisible", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)

        For Each extension As Microsoft.Reporting.WinForms.RenderingExtension In ReportViewer.LocalReport.ListRenderingExtensions()
            If (String.Compare("EXCELOPENXML", extension.Name) = 0) Then
                fieldInfo.SetValue(extension, False)
            End If
        Next
    End Sub

Habilitando formatos de exportação escondidos

Ao mesmo tempo que podemos desabilitar algumas opções de exportação utilizando reflection, podemos utilizar a mesma metodologia para habilitarmos algumas opções de exportação que ficam escondidas por padrão. Por exemplo, não sei se você sabe, mas, é possível exportarmos o relatório para os formatos antigos do Office (Word .doc e Excel .xls) e também no formato imagem.

Para habilitarmos esses formatos de exportação que ficam escondidos por padrão, ao invés de alterarmos o valor do campo “m_isVisible” para “false“, nós iremos alterá-lo para “true” em todos os casos:

        // C#
        private void reportViewer_Load(object sender, EventArgs e)
        {
            var fieldInfo = typeof(Microsoft.Reporting.WinForms.RenderingExtension).GetField("m_isVisible", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            
            foreach (var extension in this.reportViewer.LocalReport.ListRenderingExtensions())
            {
                fieldInfo.SetValue(extension, true);
            }
        }
    ' VB.NET
    Private Sub ReportViewer_Load(sender As Object, e As EventArgs) Handles ReportViewer.Load
        Dim fieldInfo As System.Reflection.FieldInfo = GetType(Microsoft.Reporting.WinForms.RenderingExtension).GetField("m_isVisible", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)

        For Each extension As Microsoft.Reporting.WinForms.RenderingExtension In ReportViewer.LocalReport.ListRenderingExtensions()
            fieldInfo.SetValue(extension, True)
        Next
    End Sub

Com isso, execute a aplicação e note que algumas novas opções de exportação apareceram:

Exportando sem o preview

Até agora nós vimos como exportar o relatório do Report Viewer utilizando o botão existente dentro do próprio controle visualizador. Agora, pode ser que você deve estar se perguntando: “tem como exportar o relatório sem o controle visualizador?“. Tem! E é justamente isso que eu vou te mostrar agora.

A classe LocalReport possui um método chamado Render. Ao chamarmos esse método passando o formato desejado (PDF, por exemplo), um array de bytes será retornado com o conteúdo da exportação. Para fazermos uso desse método, podemos simplesmente carregar uma instância de LocalReport manualmente (sem a utilização do viewer). Vamos conferir como podemos fazer isso?

O primeiro passo para demonstrarmos a exportação direta é a criação de um novo formulário. Nesse formulário, adicione dois botões (“pdfButton” e “docxButton“):

Feito isso, no code-behind, vamos adicionar um atributo do tipo LocalReport (que dei o nome de “report“) e vamos fazer o carregamento manual do relatório no construtor do formulário (ou no “Load“, caso você esteja trabalhando com VB.NET):

        // C#
        Microsoft.Reporting.WinForms.LocalReport report;

        public FormExport()
        {
            InitializeComponent();

            report = new Microsoft.Reporting.WinForms.LocalReport();
            report.ReportEmbeddedResource = "ReportViewerExport.RelatorioFuncionarios.rdlc";

            var funcionarios = new List<Funcionario>();
            funcionarios.Add(new Funcionario() { Nome = "André", Sobrenome = "Lima", DataNascimento = new DateTime(1984, 1, 1), Cargo = "Programador", DataAdmissao = new DateTime(2008, 2, 1) });
            funcionarios.Add(new Funcionario() { Nome = "Fulano", Sobrenome = "de Tal", DataNascimento = new DateTime(1973, 4, 15), Cargo = "Gerente", DataAdmissao = new DateTime(1998, 4, 1) });
            funcionarios.Add(new Funcionario() { Nome = "Beltrano", Sobrenome = "Silva", DataNascimento = new DateTime(1959, 2, 23), Cargo = "Diretor", DataAdmissao = new DateTime(1993, 1, 1) });
            funcionarios.Add(new Funcionario() { Nome = "João", Sobrenome = "Souza", DataNascimento = new DateTime(1989, 8, 14), Cargo = "Estagiário", DataAdmissao = new DateTime(2010, 6, 1), DataDemissao = new DateTime(2012, 12, 31) });

            var dataSource = new Microsoft.Reporting.WinForms.ReportDataSource("DataSetFuncionario", funcionarios);
            report.DataSources.Add(dataSource);

            report.Refresh();
        }
    ' VB.NET
    Private Report As Microsoft.Reporting.WinForms.LocalReport

    Private Sub FormExport_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Report = New Microsoft.Reporting.WinForms.LocalReport()
        Report.ReportEmbeddedResource = "ReportViewerExport.VB.RelatorioFuncionarios.rdlc"

        Dim Funcionarios As New List(Of Funcionario)
        Funcionarios.Add(New Funcionario() With {.Nome = "André", .Sobrenome = "Lima", .DataNascimento = New DateTime(1984, 1, 1), .Cargo = "Programador", .DataAdmissao = New DateTime(2008, 2, 1)})
        Funcionarios.Add(New Funcionario() With {.Nome = "Fulano", .Sobrenome = "de Tal", .DataNascimento = New DateTime(1973, 4, 15), .Cargo = "Gerente", .DataAdmissao = New DateTime(1998, 4, 1)})
        Funcionarios.Add(New Funcionario() With {.Nome = "Beltrano", .Sobrenome = "Silva", .DataNascimento = New DateTime(1959, 2, 23), .Cargo = "Diretor", .DataAdmissao = New DateTime(1993, 1, 1)})
        Funcionarios.Add(New Funcionario() With {.Nome = "João", .Sobrenome = "Souza", .DataNascimento = New DateTime(1989, 8, 14), .Cargo = "Estagiário", .DataAdmissao = New DateTime(2010, 6, 1), .DataDemissao = New DateTime(2012, 12, 31)})

        Dim dataSource As New Microsoft.Reporting.WinForms.ReportDataSource("DataSetFuncionario", Funcionarios)
        Report.DataSources.Add(dataSource)

        Report.Refresh()
    End Sub

Em seguida, vamos adicionar um método que fará a exportação do relatório, recebendo o formato da exportação e o nome do arquivo:

        // C#
        private void ExportarRelatorio(string formato, string nomeArquivo)
        {
            var bytes = report.Render(formato);
            System.IO.File.WriteAllBytes(nomeArquivo, bytes);
        }
    ' VB.NET
    Private Sub ExportarRelatorio(Formato As String, NomeArquivo As String)
        Dim Bytes = Report.Render(Formato)
        System.IO.File.WriteAllBytes(NomeArquivo, Bytes)
    End Sub

Por fim, vamos implementar o handler para o evento “Click” dos botões passando o formato desejado e o caminho do arquivo a ser gerado:

        // C#
        private void pdfButton_Click(object sender, EventArgs e)
        {
            ExportarRelatorio("PDF", "relatorio2.pdf");
        }

        private void docxButton_Click(object sender, EventArgs e)
        {
            ExportarRelatorio("WORDOPENXML", "relatorio2.docx");
        }
    ' VB.NET
    Private Sub pdfButton_Click(sender As Object, e As EventArgs) Handles pdfButton.Click
        ExportarRelatorio("PDF", "relatorio2.pdf")
    End Sub

    Private Sub docxButton_Click(sender As Object, e As EventArgs) Handles docxButton.Click
        ExportarRelatorio("WORDOPENXML", "relatorio2.docx")
    End Sub

Execute o aplicativo, clique no botão de exportação desejado e veja o resultado sendo gerado na pasta bin/debug da aplicação:

Concluindo

Exportar relatórios do Report Viewer não é uma tarefa difícil de ser realizada. Se o botão de exportação do próprio controle visualizador for o suficiente para você, a dificuldade é praticamente nula. Nesse caso, o máximo que você poderia customizar seriam os formatos para exportação, desabilitando alguns dos formatos caso necessário ou exibindo os formatos que ficam escondidos por padrão.

Por outro lado, caso você não queira disponibilizar o botão de exportação no próprio viewer ou caso você queira fazer a exportação diretamente sem exibir o relatório, podemos fazer isso através do método Render da classe LocalReport.

Todas essas opções você conferiu em detalhes neste artigo. Agora que você virou um expert em exportação de relatórios do Report Viewer, conte pra gente nos comentários: você já precisou exportar relatórios do Report Viewer na sua aplicação? Caso positivo, qual das opções você utilizou? Você sabia que alguns formatos de exportação vinham escondidos por padrão? Aguardamos os seus comentários na caixa logo abaixo!

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. 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/calculator-calculation-insurance-1044173/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como podemos exportar relatórios do Report Viewer? appeared first on André Alves de Lima.

Trabalhando com o Crystal Reports no WPF

$
0
0

Ao contrário do Report Viewer (que não possui um viewer específico para WPF), a SAP disponibiliza um controle visualizador para os relatórios do Crystal Reports no WPF. Entretanto, o viewer não é adicionado automaticamente na caixa de ferramentas do Visual Studio e nem temos muitos exemplos em português abordando esse tema. E é justamente devido a esses motivos que eu vou explicar para você no artigo de hoje como trabalhar com o Crystal Reports no WPF.

Criando o projeto e relatório de exemplo

Primeiramente, vamos começar criando um projeto do tipo “WPF Application“. Nesse projeto, vamos adicionar um DataSet de exemplo, chamado “DataSetExemplo“. Dentro do DataSet, adicione uma DataTable chamada “DataTableExemplo“, com as colunas “ColunaExemplo1” e “ColunaExemplo2“:

Feito isso, vamos adicionar no projeto um novo item do tipo “Crystal Reports“, dando o nome de “SeuCrystalReport“:

Nota: se você não estiver encontrando o item “Crystal Reports”, provavelmente você não instalou a biblioteca correta. Caso você esteja com essa dificuldade, confira o meu artigo sobre a instalação do Crystal Reports no Visual Studio para maiores detalhes.

Vamos criar um relatório bem simples, uma vez que o foco do artigo não é a criação do relatório, mas sim, a sua utilização no WPF. Se você quiser conferir um exemplo de relatório mais elaborado, confira o artigo onde eu mostrei como criar relatórios mestre/detalhe com o Crystal Reports e Entity Framework.

Dito isso, no relatório em branco, vamos executar o “Database Expert” para adicionarmos a fonte de dados para o nosso relatório:

Dentro do “Database Expert“, encontre o DataSet que criamos anteriormente e mova-o para o lado direito:

Feito isso, ajuste o layout do relatório, adicionando um título e arrastando as duas colunas para a área de detalhes do relatório, de forma que ele fique parecido com a imagem abaixo:

Pronto! Agora que temos o nosso relatório de exemplo preparado, vamos conferir como podemos exibi-lo na nossa Window.

Adicionando o viewer do WPF na ToolBox

Em primeiro lugar, temos que adicionar o controle visualizador do Crystal Reports na caixa de ferramentas do Visual Studio. O viewer do Windows Forms é adicionado automaticamente na Toolbox durante a instalação, mas, o viewer do WPF não é adicionado automaticamente (vai entender).

Para isso, temos que clicar com o botão direito na caixa de ferramentas e escolher a opção “Choose Items“:

Dentro da tela “Choose Toolbox Items“, encontre o item CrystalReportsViewer dentro da categoria “WPF Components” e marque a caixa de opções para que ele seja mostrado na ToolBox:

Clique em “OK” e você verá que o item do Crystal Reports será adicionado à categoria “General” da caixa de ferramentas:

Configurando o viewer no WPF

Com o controle visualizador adicionado à caixa de ferramentas, vamos arrastá-lo para dentro da nossa Window, mais especificamente, para dentro do Grid. Note que, ao arrastarmos o controle para dentro da Window, uma nova declaração de namespace é adicionada (xmlns:Viewer). Para conseguirmos manipular o viewer do code-behind, temos que definir um nome para o controle (x:Name). Dessa forma, dê o nome de “CrystalReportsViewer” para o controle:

<Window xmlns:Viewer="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer"  
        x:Class="CrystalReportsWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewer:CrystalReportsViewer x:Name="CrystalReportsViewer"/>
    </Grid>
</Window>

Note que, como no Windows Forms ou Web Forms, podemos definir diversas outras propriedades do controle do Crystal Reports diretamente na janela de propriedades:

Uma vez definido um nome para o controle, vamos para o code-behind da nossa Window para fazermos o carregamento do relatório. No construtor da janela, logo depois da chamada de “InitializeComponents” (ou no evento “Loaded” da Window, caso você esteja trabalhando com o VB.NET), vamos adicionar o código para fazer o carregamento:

        // C#
        public MainWindow()
        {
            InitializeComponent();

            var dataSet = new DataSetExemplo();
            dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 1.1", "Linha 1.2");
            dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 2.1", "Linha 2.2");
            dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 3.1", "Linha 3.2");
            dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 4.1", "Linha 4.2");
            dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 5.1", "Linha 5.2");

            var reportDocument = new SeuCrystalReport();
            reportDocument.SetDataSource(dataSet);

            CrystalReportsViewer.ViewerCore.ReportSource = reportDocument;
        }
    ' VB.NET
    Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Dim dataSet As New DataSetExemplo()
        dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 1.1", "Linha 1.2")
        dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 2.1", "Linha 2.2")
        dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 3.1", "Linha 3.2")
        dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 4.1", "Linha 4.2")
        dataSet.DataTableExemplo.AddDataTableExemploRow("Linha 5.1", "Linha 5.2")

        Dim reportDocument As New SeuCrystalReport()
        reportDocument.SetDataSource(dataSet)

        CrystalReportsViewer.ViewerCore.ReportSource = reportDocument
    End Sub

Pronto! Execute o aplicativo e veja o belo erro que vamos receber:

An unhandled exception of type ‘System.IO.FileNotFoundException’ occurred in mscorlib.dll. Additional information: Could not load file or assembly ‘file:///C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\dotnet1\crdb_adoplus.dll’ or one of its dependencies. The system cannot find the file specified.

Esse é um erro clássico do Crystal Reports em aplicações que utilizam o .NET Framework 4 ou superior. Corrigi-lo é muito fácil. Basta adicionarmos o elemento “useLegacyV2RuntimeActivationPolicy” com o valor “true” na tag “startup” do nosso arquivo app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true"> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Agora sim, ao executarmos o aplicativo, veremos o relatório sendo exibido corretamente:

Uma coisa que poderíamos ter feito diferente é fazer o carregamento do relatório a partir de um arquivo em disco (e não a partir de um relatório atachado ao próprio projeto, como fizemos anteriormente). Para isso, ao invés de criarmos o “reportDocument” como uma instância de “SeuCrystalReports“, temos que criar uma instância genérica de “ReportDocument” e, logo em seguida, chamamos o método “Load” passando o caminho do arquivo rpt em disco:

            // C#
            var reportDocument = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
            reportDocument.Load("caminhoDoSeuRelatorio.rpt");
            reportDocument.SetDataSource(dataSet);
        ' VB.NET
        Dim reportDocument As New CrystalDecisions.CrystalReports.Engine.ReportDocument()
        reportDocument.Load("caminhoDoSeuRelatorio.rpt")
        reportDocument.SetDataSource(dataSet)

Concluindo

Não é difícil exibirmos relatórios do Crystal Reports no WPF, uma vez que a biblioteca disponibilizada pela SAP (atual mantenedora do Crystal Reports) possui um controle visualizador do Crystal Reports para o WPF. Nesse artigo você conferiu como adicionar esse controle na caixa de ferramentas do Visual Studio e como utilizá-lo para exibir o seu primeiro relatório.

E você? Já tinha utilizado esse controle no WPF? Tem alguma observação importante? Nos conte mais detalhes logo ali embaixo 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com o Crystal Reports no WPF appeared first on André Alves de Lima.

Como distribuir aplicações com o Report Viewer?

$
0
0

Imagina que você passou um tempão desenvolvendo o seu aplicativo e gastou dias e dias desenvolvendo seus relatórios no Report Viewer. Aí você chega para instalar a aplicação no computador do cliente e ela não funciona por causa do Report Viewer?

Pois bem, como você já deve saber, o Report Viewer não é parte integrada do .NET Framework. Dessa forma, ele precisa ser instalado juntamente com a sua aplicação. Para isso, existem algumas opções. Neste artigo veremos como distribuir aplicações com o Report Viewer.

Se você utiliza o controle do Report Viewer na sua aplicação, caso ele não esteja instalado no computador cliente, você provavelmente receberá um erro parecido com este:

Neste artigo veremos três maneiras de distribuirmos o controle do Report Viewer: instalando o Report Viewer Redistributable manualmente (ou automaticamente de forma silenciosa), copiando as dlls necessárias no diretório da aplicação ou publicando o aplicativo através do ClickOnce.

Instalando o Report Viewer Redistributable

O Report Viewer Redistributable (também conhecido como “runtime“) é um instalador à parte que serve para fazer a distribuição dos controles do Report Viewer. Essa é a mesma runtime que temos que instalar para utilizar o Report Viewer no Visual Studio Express. A runtime do Report Viewer está disponível para download no site da Microsoft e você deve baixar a versão correspondente com o seu Visual Studio. Por exemplo, este é o link para a runtime do Report Viewer 2015.

A runtime do Report Viewer pode ser instalada manualmente ou integrada a um outro tipo de instalador. Uns tempos atrás eu mostrei como criar instaladores para aplicativos .NET. Você poderia integrar a instalação silenciosa da runtime do Report Viewer em todas as três ferramentas demonstradas naquele artigo. Para isso, basta utilizar a seguinte linha de comando:

ReportViewer.exe /q:a /c:"install.exe /q"

Com essa linha de comando, o instalador da runtime do Report Viewer será executado, os controles do Report Viewer serão instalados e tudo isso sem que o usuário nem perceba (tudo de forma silenciosa).

Copiando as dlls necessárias no diretório da aplicação

Algumas vezes não temos a possibilidade de criarmos instaladores para os nossos aplicativos e queremos copiar somente os arquivos necessários para a aplicação no computador cliente. Mas, como é que fazemos no caso da aplicação utilizar o Report Viewer como ferramenta de relatórios?

Por sorte, todas as funcionalidades do Report Viewer estão centralizadas em algumas poucas dlls (não é à toa que a runtime do Report Viewer 2015 tem somente 8.9Mb). Dessa forma, a única coisa que precisamos saber é quais dlls são necessárias ao Report Viewer e onde elas estão localizadas, de forma que consigamos copiá-las do computador onde desenvolvemos o aplicativo para o computador onde a aplicação será executada.

As dlls utilizadas pelo Report Viewer são:

– Microsoft.ReportViewer.Common.dll
– Microsoft.ReportViewer.WinForms.dll
– Microsoft.ReportViewer.ProcessingObjectModel.dll
– Microsoft.SqlServer.Types.dll

E onde é que elas estão localizadas? No GAC! Para acessarmos o GAC, abra a janela “executar” do Windows e digite o caminho “C:\Windows\Assembly\GAC_MSIL“):

Não tente navegar até essa pasta pelo Windows Explorer porque não funcionará. Você tem que abri-la a partir da tela “executar” para realmente conseguir explorar o seu conteúdo. Dentro do GAC_MSIL, encontre as pastas referentes a cada uma das dlls listadas acima como, por exemplo, a pasta “Microsoft.ReportViewer.Common“:

Dentro dessa pasta, você encontrará cada uma das versões dessa dll que você tem instalada no seu computador. No meu caso, por exemplo, eu tenho as versões 10, 11 e 12:

Você precisa copiar a dll da versão que você está utilizando na sua aplicação. Para descobrir qual é a versão correta, vá até as referências do projeto, clique em uma das referências do Report Viewer e confira o item “Version” na janela de propriedades:

Basta repetir esse processo para cada uma das dlls listadas anteriormente, copiá-las para o diretório da aplicação no computador cliente e pronto! Seus relatórios funcionarão normalmente.

Publicando o aplicativo através do ClickOnce

Outra ferramenta muito utilizada na instalação de aplicações desktop é o ClickOnce. Essa opção está disponível em todas as edições do Visual Studio através da aba “Publicar” das propriedades do projeto:

A distribuição do Report Viewer pelo ClickOnce é extremamente simples, basta configurarmos o Report Viewer como pré-requisito da aplicação nas propriedades do ClickOnce. Para isso, clique no botão “Prerequisites” e marque a opção “Microsoft Report Viewer 2012 Runtime“:

Feito isso, o mecanismo do ClickOnce automaticamente baixará a runtime do Report Viewer e instalará juntamente com a aplicação.

Qual metodologia utilizar?

Sem sombra de dúvidas, a opção mais simples de todas é instalar manualmente a runtime no computador onde o aplicativo será executado (ou no servidor, caso você esteja lidando com um website ASP.NET). Porém, temos que lembrar que a primeira impressão do nosso sistema é a que fica, e pedir para que o usuário instale manualmente a runtime do Report Viewer com certeza não causará uma primeira boa impressão.

A alternativa mais profissional é realizar a instalação silenciosa da runtime durante o processo de instalação do aplicativo. Essa é a maneira mais fácil (e profissional) de fazer a instalação do Report Viewer. Se você estiver utilizando um instalador para distribuir a sua aplicação, nem pense duas vezes e utilize essa sistemática para distribuir o Report Viewer.

Quanto à opção de copiar os arquivos diretamente no diretório da aplicação, a vantagem é que esse tipo de distribuição é muito simples. Basta copiarmos esses arquivos em uma pasta no computador destino e a aplicação funcionará sem maiores problemas. A desvantagem é que ela não é nada profissional, uma vez que fazer com que o seu cliente copie arquivos de um lado para o outro definitivamente não causará uma boa impressão. Uma alternativa muito interessante é mesclarmos essa técnica com a utilização de um instalador (ou até mesmo um self-extract do WinRAR).

Por fim, a opção do ClickOnce também é muito simples no que diz respeito à distribuição do Report Viewer. Basta marcarmos uma caixa e tudo estará pronto. Porém, nesse caso temos a desvantagem que o ClickOnce demanda uma certa infraestrutura, uma vez que o pacote de distribuição deverá ser publicado em algum servidor IIS ou na rede local, e essa não é uma tarefa muito trivial. Além disso, os aplicativos instalados pelo ClickOnce não são armazenados na pasta “Arquivos de Programas” e não é possível escolhermos o seu local de instalação. Isso pode trazer problemas caso você esteja esperando que o aplicativo seja instalado em uma pasta específica ou dentro da pasta “Arquivos de Programas“.

Concluindo

Neste artigo você conferiu três possibilidades para distribuir as suas aplicações que utilizam o controle do Report Viewer. Além disso, vimos também as vantagens e desvantagens de cada uma dessas modalidades.

E você, já precisou distribuir o Report Viewer para os seus clientes? Qual opção de distribuição você utilizou? Conte-nos na caixa de comentários como é que foram as suas experiências.

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

Photo by Peter Shanks used under Creative Commons
https://www.flickr.com/photos/botheredbybees/1426877411

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como distribuir aplicações com o Report Viewer? appeared first on André Alves de Lima.

Como podemos exportar relatórios do Crystal Reports?

$
0
0

Há algumas semanas atrás eu mostrei como exportar relatórios do Report Viewer. Esta semana eu decidi escrever novamente sobre o tema exportação de relatórios, só que dessa vez mostrando como exportar relatórios do Crystal Reports.

Muito parecido com o Report Viewer, nos controles do Crystal Reports podemos habilitar ou desabilitar a exportação por completo e escolher quais formatos de exportação estarão disponíveis. Além disso, caso seja necessário, conseguimos também fazer a exportação sem utilizar o controle visualizador (ou seja, somente com as dlls de negócio do Crystal Reports). E aí, bora conferir como conseguimos fazer tudo isso?

Criando o projeto de exemplo

Como mencionado anteriormente, o objetivo desse artigo é mostrar coisas relacionadas à exportação dos relatórios do Crystal Reports. Dessa forma, criaremos um relatório extremamente simples em um projeto “Windows Forms“, que mostrará uma listagem de Clientes. Se você quiser dar uma olhada em um tutorial que mostra um relatório mais complexo do Crystal Reports, confira o meu artigo sobre Crystal Reports com Entity Framework.

Primeiramente, vamos começar criando um projeto do tipo “Windows Forms” e, dentro desse projeto, vamos adicionar uma nova classe chamada “Cliente“:

    // C#
    public class Cliente
    {
        public string Nome { get; set; }
        public DateTime DataCadastro { get; set; }
        public string Regiao { get; set; }
        public DateTime DataPrimeiraCompra { get; set; }
        public DateTime DataUltimaCompra { get; set; }
    }
' VB.NET
Public Class Cliente
    Public Property Nome As String
    Public Property DataCadastro As DateTime
    Public Property Regiao As String
    Public Property DataPrimeiraCompra As DateTime
    Public Property DataUltimaCompra As DateTime
End Class

Feito isso, vamos adicionar um novo relatório do Crystal Reports dentro da nossa aplicação, dando o nome de “RelatorioClientes“. Os relatórios do Crystal Reports ficam dentro da categoria “Reporting” da tela “Add New Item“:

Nota: Caso você não consiga encontrar o item “Crystal Reports” na tela “Add New Item”, isso quer dizer que você provavelmente não instalou a versão correta do Crystal Reports. Para resolver esse problema, confira o meu artigo onde eu mostro como instalar o Crystal Reports no Visual Studio 2013 e 2015.

Uma vez adicionado o relatório no projeto, precisamos escolher o conjunto de dados que alimentará o relatório. Para isso, vamos clicar com o botão direito em “Database Fields” e, logo em seguida, escolhemos a opção “Database Expert“:

Na tela “Database Expert“, encontre a classe “Cliente” e mova-a para o lado direito da tela:

Por fim, arraste os campos para dentro do relatório de forma que ele fique parecido com a imagem abaixo:

Pronto! Agora vamos até o formulário, encontramos o controle do Crystal Reports na caixa de ferramentas do Visual Studio e arrastamos para dentro do nosso formulário:

Em seguida, escolhemos o relatório que criamos anteriormente:

Agora vamos até o code-behind do formulário para criarmos uma lista de instâncias da classe “Cliente” e passamos essa lista como DataSource do relatório:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();

            var clientes = new List<Cliente>();
            clientes.Add(new Cliente() { Nome = "André Lima", DataCadastro = new DateTime(2008, 5, 17), Regiao = "Norte", DataPrimeiraCompra = new DateTime(2008, 6, 1), DataUltimaCompra = new DateTime(2016, 3, 12) });
            clientes.Add(new Cliente() { Nome = "Fulano de Tal", DataCadastro = new DateTime(2015, 12, 15), Regiao = "Nordeste", DataPrimeiraCompra = new DateTime(2016, 1, 16), DataUltimaCompra = new DateTime(2016, 1, 16) });
            clientes.Add(new Cliente() { Nome = "Beltrano Silva", DataCadastro = new DateTime(2011, 2, 23), Regiao = "Sudeste", DataPrimeiraCompra = new DateTime(2011, 2, 23), DataUltimaCompra = new DateTime(2013, 4, 12) });
            clientes.Add(new Cliente() { Nome = "João Souza", DataCadastro = new DateTime(2014, 8, 14), Regiao = "Sul", DataPrimeiraCompra = new DateTime(2014, 8, 24), DataUltimaCompra = new DateTime(2015, 12, 31) });
            RelatorioClientes1.SetDataSource(clientes);
            crystalReportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Clientes As New List(Of Cliente)
        Clientes.Add(New Cliente() With {.Nome = "André Lima", .DataCadastro = New DateTime(2008, 5, 17), .Regiao = "Norte", .DataPrimeiraCompra = New DateTime(2008, 6, 1), .DataUltimaCompra = New DateTime(2016, 3, 12)})
        Clientes.Add(New Cliente() With {.Nome = "Fulano de Tal", .DataCadastro = New DateTime(2015, 12, 15), .Regiao = "Nordeste", .DataPrimeiraCompra = New DateTime(2016, 1, 16), .DataUltimaCompra = New DateTime(2016, 1, 16)})
        Clientes.Add(New Cliente() With {.Nome = "Beltrano Silva", .DataCadastro = New DateTime(2011, 2, 23), .Regiao = "Sudeste", .DataPrimeiraCompra = New DateTime(2011, 2, 23), .DataUltimaCompra = New DateTime(2013, 4, 12)})
        Clientes.Add(New Cliente() With {.Nome = "João Souza", .DataCadastro = New DateTime(2014, 8, 14), .Regiao = "Sul", .DataPrimeiraCompra = New DateTime(2014, 8, 24), .DataUltimaCompra = New DateTime(2015, 12, 31)})
        RelatorioClientes1.SetDataSource(Clientes)
        CrystalReportViewer1.RefreshReport()
    End Sub

Por fim, antes de executarmos o nosso projeto precisamos configurar o elemento “useLegacyV2RuntimeActivationPolicy” como “true” no nosso arquivo app.config, caso contrário receberemos um erro ao tentarmos executar a nossa aplicação:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Agora sim, execute o projeto e veja o resultado:

Desabilitando a exportação

Ao utilizarmos o controle padrão do Crystal Reports, podemos exportar os relatórios utilizando o primeiro botão da barra de ferramentas:

Na janela que se abre, podemos escolher o local onde a exportação será salva e o formato que queremos utilizar:

E se quisermos desabilitar o botão de exportação no controle visualizador? Simples, basta alterarmos a propriedade “ShowExportButton” para “false“:

Desabilitando alguns formatos de exportação

OK, acabamos de ver logo acima como podemos desabilitar por completo a exportação do relatório. Mas, e se quisermos habilitar somente alguns formatos específicos de exportação? Por exemplo, digamos que nós só queiramos deixar habilitado para o usuário os formatos “PDF” e “Excel“. Como é que podemos fazer isso?

Não se preocupe, o processo é muito fácil e não envolve nenhuma gambiarra (diferente do que vimos no caso do Report Viewer, com o qual precisamos utilizar reflection para conseguir desabilitar alguns formatos de exportação!). A única coisa que temos que fazer é alterar a propriedade AllowedExportFormats passando a lista de formatos que queremos deixar habilitado:

// C#
crystalReportViewer1.AllowedExportFormats = (int)(CrystalDecisions.Shared.ViewerExportFormats.PdfFormat | CrystalDecisions.Shared.ViewerExportFormats.ExcelFormat);
' VB.NET
CrystalReportViewer1.AllowedExportFormats = (CrystalDecisions.Shared.ViewerExportFormats.PdfFormat Or CrystalDecisions.Shared.ViewerExportFormats.ExcelFormat)

Dessa forma, a lista de formatos será limitada conforme configuramos no nosso código:

Exportando sem o preview

Tudo isso que vimos até agora vale para o caso em que exibiremos o relatório no controle visualizador do Crystal Reports. Mas, e se não quisermos exibir o relatório? E se quisermos exportá-lo diretamente para o disco ou para uma Stream? Nesse caso, temos que utilizar os métodos “Export*” do ReportDocument:

Como você pode observar na imagem acima, temos algumas opções de métodos que começam com a palavra “Export“. Primeiramente, temos o método “Export“, que é o mais flexível de todos. Com ele, temos que configurar um objeto do tipo ExportOptions, onde informamos se queremos exportar para o disco, o caminho, o formato, etc. Devido à complexidade desse método, não recomendo a sua utilização. Conseguimos facilmente satisfazer às nossas necessidades com os outros métodos que começam com “Export“.

O método “ExportToDisk“, como o próprio nome diz, exporta o relatório para o disco. Na chamada desse método, temos que passar o formato desejado e o caminho onde queremos salvar o relatório exportado. Já o método “ExportToHttpResponse” exporta o relatório para uma HttpResponse. Essa opção só faz sentido para aplicações web, onde podemos exportar o relatório diretamente para uma janela do browser ou para exibir o diálogo de “salvar arquivo” no lado cliente. Por fim, o método “ExportToStream“, como o nome diz, exporta o relatório para uma Stream. Nesse caso podemos escolher salvar essa Stream em um banco de dados, por exemplo.

Neste artigo eu vou mostrar para você a utilização do método “ExportToDisk“. Primeiramente, vamos criar um novo formulário no nosso projeto, contendo um botão para exportar o relatório em PDF e outro para exportar o relatório em DOC:

Uma vez ajustado o layout do formulário, vamos adicionar o código no code-behind para fazermos o carregamento do relatório em memória:

        // C#
        private RelatorioClientes _relatorio = new RelatorioClientes();

        public FormExport()
        {
            InitializeComponent();

            var clientes = new List<Cliente>();
            clientes.Add(new Cliente() { Nome = "André Lima", DataCadastro = new DateTime(2008, 5, 17), Regiao = "Norte", DataPrimeiraCompra = new DateTime(2008, 6, 1), DataUltimaCompra = new DateTime(2016, 3, 12) });
            clientes.Add(new Cliente() { Nome = "Fulano de Tal", DataCadastro = new DateTime(2015, 12, 15), Regiao = "Nordeste", DataPrimeiraCompra = new DateTime(2016, 1, 16), DataUltimaCompra = new DateTime(2016, 1, 16) });
            clientes.Add(new Cliente() { Nome = "Beltrano Silva", DataCadastro = new DateTime(2011, 2, 23), Regiao = "Sudeste", DataPrimeiraCompra = new DateTime(2011, 2, 23), DataUltimaCompra = new DateTime(2013, 4, 12) });
            clientes.Add(new Cliente() { Nome = "João Souza", DataCadastro = new DateTime(2014, 8, 14), Regiao = "Sul", DataPrimeiraCompra = new DateTime(2014, 8, 24), DataUltimaCompra = new DateTime(2015, 12, 31) });
            _relatorio.SetDataSource(clientes);
            _relatorio.Refresh();
        }
    ' VB.NET
    Private Relatorio As New RelatorioClientes()

    Private Sub FormExport_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Clientes As New List(Of Cliente)
        Clientes.Add(New Cliente() With {.Nome = "André Lima", .DataCadastro = New DateTime(2008, 5, 17), .Regiao = "Norte", .DataPrimeiraCompra = New DateTime(2008, 6, 1), .DataUltimaCompra = New DateTime(2016, 3, 12)})
        Clientes.Add(New Cliente() With {.Nome = "Fulano de Tal", .DataCadastro = New DateTime(2015, 12, 15), .Regiao = "Nordeste", .DataPrimeiraCompra = New DateTime(2016, 1, 16), .DataUltimaCompra = New DateTime(2016, 1, 16)})
        Clientes.Add(New Cliente() With {.Nome = "Beltrano Silva", .DataCadastro = New DateTime(2011, 2, 23), .Regiao = "Sudeste", .DataPrimeiraCompra = New DateTime(2011, 2, 23), .DataUltimaCompra = New DateTime(2013, 4, 12)})
        Clientes.Add(New Cliente() With {.Nome = "João Souza", .DataCadastro = New DateTime(2014, 8, 14), .Regiao = "Sul", .DataPrimeiraCompra = New DateTime(2014, 8, 24), .DataUltimaCompra = New DateTime(2015, 12, 31)})
        Relatorio.SetDataSource(Clientes)
        Relatorio.Refresh()
    End Sub

Por fim, vamos implementar o handler para o evento “Click” de cada um dos botões, onde faremos a exportação do relatório utilizando o formato escolhido:

        // C#
        private void pdfButton_Click(object sender, EventArgs e)
        {
            _relatorio.ExportToDisk(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat, "relatorio2.pdf");
        }

        private void docButton_Click(object sender, EventArgs e)
        {
            _relatorio.ExportToDisk(CrystalDecisions.Shared.ExportFormatType.WordForWindows, "relatorio2.doc");
        }
    ' VB.NET
    Private Sub pdfButton_Click(sender As Object, e As EventArgs) Handles pdfButton.Click
        Relatorio.ExportToDisk(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat, "relatorio2.pdf")
    End Sub

    Private Sub docButton_Click(sender As Object, e As EventArgs) Handles docButton.Click
        Relatorio.ExportToDisk(CrystalDecisions.Shared.ExportFormatType.WordForWindows, "relatorio2.doc")
    End Sub

Pronto! Execute a aplicação mostrando o formulário de exportação, clique em cada um dos botões e veja o resultado sendo gerado no diretório da aplicação:

Concluindo

Nesse artigo você conferiu tudo relacionado à exportação de relatórios do Crystal Reports, que, como você conferiu, é um processo muito simples. Em primeiro lugar, temos a opção de exportar diretamente no controle visualizador do Crystal Reports. Conseguimos, inclusive, escolher quais os formatos de exportação estarão disponíveis para o usuário. Além disso, temos também a opção de exportar os relatórios do Crystal Reports sem utilizar o controle visualizador, diretamente via código.

E você, já precisou exportar relatórios do Crystal Reports na sua aplicação? Sabia que dava para escolher os formatos de exportação disponíveis para o usuário? Como foi a sua experiência quanto à exportação de relatórios do Crystal Reports? Os arquivos gerados foram satisfatórios? Conte-nos os detalhes na caixa de comentários logo abaixo!

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Image by Jake Przespo used under Creative Commons
https://www.flickr.com/photos/jakeprzespo/4566115233/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como podemos exportar relatórios do Crystal Reports? appeared first on André Alves de Lima.

Será que é possível exibir RichText no Report Viewer?

$
0
0

O artigo de hoje vem para responder uma dúvida de um leitor do site: será que é possível exibir RichText no Report Viewer ? O que você acha? Eu não sabia se era possível eu não, mas, acabei descobrindo a resposta depois de pesquisar um bocado. Vamos conferir o resultado?

Criando um RTF de exemplo

Antes de começar, vamos criar um exemplo de RTF. Nesse RTF, vamos colocar alguns tipos de textos, com headings, formatações e bullet points. Além disso, vamos colocar uma tabela e uma imagem. O resultado você confere abaixo:

Você pode baixar esse RTF de exemplo clicando aqui.

Convertendo o RTF para HTML

O Report Viewer não suporta a exibição de RichText nativamente, somente HTML. Dessa forma, a primeira coisa que temos que fazer com o nosso RTF é converte-lo para HTML. Existem alguns exemplos e bibliotecas que fazem a conversão de RTF para HTML (e vice-versa). Vamos analisar o resultado de alguns desses componentes a seguir.

Aspose.Words for .NET: esse é um dos componentes pagos que faz a conversão de RTF para HTML. No momento da escrita desse artigo, a licença dele custa mil dólares. A sua utilização é muito simples, basta criarmos uma instância de Document passando o caminho do RTF e, logo em seguida, temos que chamar o método Save passando o caminho do HTML que queremos gerar.

            // C#
            var asposeDoc = new Aspose.Words.Document("exemplortf.rtf");
            asposeDoc.Save("exemplortf.html");
            ' VB.NET
            Dim AsposeDoc As New Aspose.Words.Document("exemplortf.rtf")
            AsposeDoc.Save("exemplortf.html")

Veja aqui o resultado do HTML gerado por ele.

SubSystems HTML – RTF Convertor: outro componente pago que faz a conversão de RTF para HTML. A licença custa atualmente 549 dólares, ou seja, metade do valor da licença do Aspose. Porém, a sua utilização é muito mais complicada do que o Aspose, demandando muitas e muitas linhas de código para fazer uma simples conversão. O resultado do HTML gerado por essa biblioteca você encontra aqui.

SautinSoft RTF to HTML .Net: mais uma biblioteca paga que possibilita a conversão de RTF para HTML. Dentre todas as bibliotecas pagas que eu testei, essa é a mais em conta, custando atualmente 239 dólares. Sua utilização é muito simples:

// C#
var conversor = new SautinSoft.RtfToHtml();
conversor.OpenRtf("exemplortf.rtf");
conversor.OutputFormat = SautinSoft.RtfToHtml.eOutputFormat.HTML_5;
conversor.ToHtml("exemplortf.html");
' VB.NET
Dim Conversor As New SautinSoft.RtfToHtml()
Conversor.OpenRtf("exemplortf.rtf")
Conversor.OutputFormat = SautinSoft.RtfToHtml.eOutputFormat.HTML_5
Conversor.ToHtml("exemplortf.html")

Com essa biblioteca eu consegui até gerar um HTML que contém a imagem “embedada” (para ver se faz alguma diferença no output do Report Viewer). Confira o resultado aqui.

MSDN Code Gallery – Converting between RTF and HTML: essa foi a primeira opção gratuita que eu testei e olha só aqui o lixo de resultado. Se a biblioteca funcionasse corretamente, o código para fazer a conversão seria muito simples:

            // C#
            var markupConverter = new MarkupConverter();
            var rtf = System.IO.File.ReadAllText("exemplortf.rtf");
            var html = markupConverter.ConvertRtfToHtml(rtf);
            System.IO.File.WriteAllText("exemplortf.html", html);
            ' VB.NET
            Dim MarkupConverter As new MarkupConverter()
            Dim Rtf = System.IO.File.ReadAllText("exemplortf.rtf")
            Dim Html = MarkupConverter.ConvertRtfToHtml(rtf)
            System.IO.File.WriteAllText("exemplortf.html", Html)

Mas, não adianta, para muitas coisas temos que acabar desembolsando uma grana para conseguirmos ter um resultado satisfatório.

CodeProject – Writing Your Own RTF Converter: a última opção de conversor RTF para HTML que eu testei foi esse projeto do CodeProject. A solução é gigantesca, contendo diversos projetos diferentes, mas, mesmo assim, compila certinho. O resultado da conversão foi este. Como você pode perceber, a tabela e a imagem foram para o espaço, mas, as formatações e bullet points ficaram firmes e fortes. Veja só o código para fazer a conversão com essa biblioteca:

// C#
IRtfDocument rtfDocument = RtfInterpreterTool.BuildDoc(ConversionText);
RtfHtmlConverter htmlConverter = new RtfHtmlConverter(rtfDocument);
var html = htmlConverter.Convert();
' VB.NET
Dim RtfDocument = RtfInterpreterTool.BuildDoc(ConversionText)
Dim HtmlConverter As New RtfHtmlConverter(RtfDocument)
Dim Html = HtmlConverter.Convert()

Exibindo o HTML no Report Viewer

A renderização de HTML no Report Viewer é limitada. Como você pode conferir na documentação, somente algumas tags do HTML são suportadas. Além disso, o CSS e estilos são muito limitados. Agora fica a dúvida: como será que essas 5 versões diferentes do RTF convertido para HTML serão renderizadas pelo Report Viewer?

Primeiramente, para testarmos a renderização, vamos criar um projeto do tipo “Windows Forms Application” e, dentro desse projeto, vamos adicionar um novo relatório do Report Viewer. Dê o nome de “RelatorioHtml” para esse novo relatório:

Dentro desse relatório, vamos criar um novo parâmetro chamado “Html“:

Feito isso, temos que arrastar o novo parâmetro para dentro do relatório e redimensiona-lo de forma que ele ocupe toda a área de detalhes do relatório:

Em seguida, temos que alterar as propriedades do placeholder de forma que o HTML seja interpretado pela engine do Report Viewer. Para isso, abrimos a edição do TextBox, clicamos com o botão direito sobre “[@Html]” e escolhemos a opção “Placeholder Properties“:

Na tela que se abre, temos que alterar o “Markup type” para HTML:

Pronto! Agora que temos o nosso relatório concluído, vamos ajustar o formulário para exibi-lo. Abra novamente o formulário, arraste um controle do Report Viewer para dentro dele, ajuste o docking de forma que ele ocupe o espaço inteiro do formulário e escolha o relatório que acabamos de criar:

Em seguida, vamos até o code-behind para passarmos o conteúdo do HTML para o parâmetro que criamos no relatório:

        // C#
        private void FormRelatorio_Load(object sender, EventArgs e)
        {
            var html = System.IO.File.ReadAllText("aspose.html");
            //var html = System.IO.File.ReadAllText("subsystems.html");
            //var html = System.IO.File.ReadAllText("sautinsoft.html");
            //var html = System.IO.File.ReadAllText("codegallery.html");
            //var html = System.IO.File.ReadAllText("ownconverter.html");
            this.reportViewer1.LocalReport.SetParameters(new Microsoft.Reporting.WinForms.ReportParameter("Html", html));
            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Html = System.IO.File.ReadAllText("aspose.html")
        'Dim Html = System.IO.File.ReadAllText("subsystems.html")
        'Dim Html = System.IO.File.ReadAllText("sautinsoft.html")
        'Dim Html = System.IO.File.ReadAllText("codegallery.html")
        'Dim Html = System.IO.File.ReadAllText("ownconverter.html")
        Me.ReportViewer1.LocalReport.SetParameters(New Microsoft.Reporting.WinForms.ReportParameter("Html", Html))
        Me.ReportViewer1.RefreshReport()
    End Sub

Como você pode perceber, podemos testar cada uma das conversões de RTF para HTML removendo o comentário da linha que está fazendo o carregamento desejado e comentando todas as outras linhas. Antes de executar a aplicação, precisamos copiar os resultados das conversões para o diretório bin/debug da aplicação. Eu compactei todos os resultados em um arquivo só e você pode baixa-lo através deste link.

Se você não quiser perder o seu tempo, aqui vão as imagens que representam o resultado de cada uma das conversões:

Aspose

SubSystems

SautinSoft

Code Gallery

CodeProject

E aí, ficou decepcionado(a)? Pois eu fiquei. Como você pode bem perceber pelos resultados apresentados acima, o HTML do Report Viewer é bem limitado. Em nenhum dos casos ele conseguiu renderizar a tabela e muito menos a imagem.

E no Crystal Reports, será que ficaria melhor?

Talvez você já tenha ouvido falar que o Crystal Reports suporta RTF e HTML por padrão nos controles de texto, então, pode ser que você esteja pensando em desenvolver seus relatórios no Crystal Reports para aproveitar o RTF ou HTML nos seus relatórios. Mas, será que a renderização desses formatos é tão melhor assim no Crystal Reports?

Primeiro eu fiz um teste tentando renderizar o próprio RTF em um relatório do Crystal Reports:

Ele se saiu melhor do que todos os resultados do Report Viewer, mas, mesmo assim, ainda não chega nem perto de corresponder ao RTF original. As linhas de grade da tabela e a imagem não são exibidas.

E, para finalizar, eu fiz também o teste com cada um dos HTMLs convertidos:

Aspose

SubSystems

SautinSoft

Code Gallery

CodeProject

Mais uma vez, não obtivemos um resultado razoável com nenhuma das opções de HTML. Ou seja, o Crystal Reports também não é lá tão bom assim no quesito renderização de RTF e HTML.

E agora? O que eu faço?

Agora que já vimos que nem o Report Viewer nem o Crystal Reports apresenta um bom resultado na exibição de RTF e HTML, o que podemos fazer para resolver esse problema? Na minha opinião, nos resta dois cenários:

– Desenhar o RTF diretamente como um relatório do Report Viewer
– Não utilizar a engine do Report Viewer para exibir o RTF

A primeira opção é bem óbvia. Se desenharmos o relatório diretamente no Report Viewer, aí não tem erro. As ferramentas de design do Report Viewer são mais do que suficientes para gerar os mais diversos tipos de layouts de relatórios. Dependendo da complexidade do RTF, vai dar um certo trabalho, mas, com certeza você conseguirá portar o seu RTF para um relatório do Report Viewer sem muitos problemas.

Já a segunda opção é abandonar o Report Viewer para esse relatório específico e imprimir o RTF diretamente para a impressora ou utilizando um PrintPreview. Veja alguns exemplos dessa implementação:

How to print the content of a RichTextBox control by using Visual C# .NET or Visual C# 2005
CodeProject: RichTextBoxDocument

Concluindo

A conclusão que você conferiu nesse artigo é que a engine do Report Viewer não consegue renderizar muito bem RichText. Como o Report Viewer não suporta a exibição de RTF nativamente (só HTML), tentamos converter o RTF para HTML utilizando várias ferramentas (pagas e gratuitas), mas, o mecanismo de renderização HTML do Report Viewer é muito limitado e o resultado não foi satisfatório.

Testamos também a mesma funcionalidade do Crystal Reports, que se saiu um pouquinho melhor, mas, mesmo assim, bem longe do ideal. Nesse caso, só nos resta reconstruir o relatório diretamente no Report Viewer ou imprimir o RTF com um PrintPreview (sem o Report Viewer ou Crystal Reports).

O que você achou desse experimento? Você já precisou de algo assim nos seus relatórios? Como é que você acabou resolvendo? Conte-nos mais detalhes na caixa de comentários logo abaixo.

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Photo by Pixabay used under Creative Commons
https://pixabay.com/en/fountain-pen-text-leave-white-442066/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Será que é possível exibir RichText no Report Viewer? appeared first on André Alves de Lima.

Trabalhando com gráficos no Crystal Reports

$
0
0

Uma das melhores maneiras de representar dados estatísticos (se não a melhor) é através de gráficos. Umas semanas atrás eu mostrei para você como trabalhar com gráficos no Report Viewer. Mas, como é que ficam as pessoas que trabalham com o Crystal Reports? Será que a criação de gráficos no Crystal Reports é similar ao Report Viewer? É justamente isso que vamos conferir neste artigo.

Preparando o projeto de exemplo

No artigo sobre gráficos com o Report Viewer, conferimos alguns gráficos estatísticos sobre vendas de produtos. Como eu gosto de sempre variar as áreas de negócios dos exemplos apresentados no site, neste artigo vamos trabalhar com um outro tipo de informação.

Para termos melhor controle da utilização do nosso sistema, normalmente implementamos algum tipo de log de informações. Nesse log temos as informações de qual usuário executou qual funcionalidade. Além disso, normalmente temos também a informação de qual sistema operacional o usuário está utilizando.

Claro que esse processo pode ser muito facilitado ao utilizarmos o Application Insights, como eu já demonstrei no passado. Mas, em alguns casos acabamos preferindo fazer a implementação desse log de forma local.

Com as informações de acesso em mãos, esse acaba sendo um cenário perfeito para desenvolvermos um relatório com gráficos, monstrando as estatísticas de acesso por usuário, quais são as principais funcionalidades que estão sendo utilizadas, qual é a carga diária, etc. Vamos ver como podemos criar gráficos com essas informações no Crystal Reports?

Primeiramente, vamos criar um projeto do tipo “Windows Forms Application“. Nesse projeto, vamos adicionar uma classe chamada “LogFuncionalidade“:

    // C#
    public class LogFuncionalidade
    {
        public string Funcionalidade { get; set; }
        public DateTime DataLog { get; set; }
        public string Usuario { get; set; }
        public string SistemaOperacional { get; set; }

        private static Random _rand = new Random();

        public static LogFuncionalidade GerarExemplo()
        {
            var funcionalidades = new string[] { "Tela de Vendas", "Cadastro de Funcionários", "Dashboard de Vendas", "Cadastro de Clientes", "Relatório Gerencial" };
            var usuarios = new string[] { "André Lima", "Fulano de Tal", "Beltrano da Silva" };
            var sistemasOperacionais = new string[] { "Windows", "iOS", "Android", "Web" };

            return new LogFuncionalidade()
            {
                Funcionalidade = funcionalidades[_rand.Next(5)],
                DataLog = DateTime.Now.AddDays(_rand.Next(30)),
                Usuario = usuarios[_rand.Next(3)],
                SistemaOperacional = sistemasOperacionais[_rand.Next(4)],
            };
        }
    }
' VB.NET
Public Class LogFuncionalidade
    Public Property Funcionalidade As String
    Public Property DataLog As DateTime
    Public Property Usuario As String
    Public Property SistemaOperacional As String

    Private Shared Rand As New Random()

    Public Shared Function GerarExemplo() As LogFuncionalidade
        Dim Funcionalidades = New String() {"Tela de Vendas", "Cadastro de Funcionários", "Dashboard de Vendas", "Cadastro de Clientes", "Relatório Gerencial"}
        Dim Usuarios = New String() {"André Lima", "Fulano de Tal", "Beltrano da Silva"}
        Dim SistemasOperacionais = New String() {"Windows", "iOS", "Android", "Web"}

        Return New LogFuncionalidade() With {
            .Funcionalidade = Funcionalidades(Rand.Next(5)),
            .DataLog = DateTime.Now.AddDays(Rand.Next(30)),
            .Usuario = Usuarios(Rand.Next(3)),
            .SistemaOperacional = SistemasOperacionais(Rand.Next(4))
        }
    End Function
End Class

Como você pode perceber, para criarmos dados fictícios de acessos ao sistema, implementamos o método “GerarExemplo“, que será chamado posteriormente múltiplas vezes para gerarmos múltiplos exemplos de logs.

Agora que já temos a nossa classe, podemos começar a desenvolver o nosso relatório. Adicione um novo relatório do Crystal Reports, dando o nome de “RelatorioUtilizacaoSistema” e escolhendo a opção “Relatório em Branco“:

Com o relatório criado, clicamos com o botão direito em “Database Fields” e escolhemos a opção “Database Expert“:

Na tela do assistente de banco de dados, temos que encontrar e mover a nossa classe “LogFuncionalidade” para a lista do lado direito da tela:

Pronto! Agora que já temos o nosso relatório com a fonte de dados configurada, vamos começar a configurar os nossos gráficos.

Gráfico pizza

O primeiro gráfico que vamos adicionar no relatório é um gráfico de pizza. Se estivéssemos trabalhando com um relatório mais complexo, nós poderíamos adicionar gráficos diferentes para cada agrupamento ou até mesmo para cada linha de detalhe. No nosso caso, como o relatório terá somente uma página sumarizando todos os dados em apenas um bloco, nós vamos esconder todas as áreas do relatório, com exceção do cabeçalho. É justamente no cabeçalho do relatório que vamos adicionar todos os nossos gráficos.

Para adicionar um gráfico no Crystal Reports, clicamos com o botão direito na área onde desejamos adiciona-lo e escolhemos a opção “Insert => Chart“:

Como queremos adicionar um gráfico de pizza, temos que primeiramente escolher a opção “Gráfico de setores” na aba “Tipo“:

Feito isso, vamos até a aba “Dados” e adicionamos o campo “Usuario” tanto na parte de agrupamento (lista superior) quanto na parte de valores (lista inferior):

Note que a operação de sumarização escolhida por padrão pelo Crystal Reports foi a contagem de registros (porque o campo “Usuario” não é numérico). Caso quiséssemos alterar a operação de sumarização, nós só precisaríamos clicar no botão “Definir Operação de Resumo“:

Por fim, para configurarmos um texto customizado para o título do gráfico, vamos até a aba “Texto“, desmarcamos a opção “Texto Automático” do título e daremos o título de “Ações por Usuário” para esse gráfico de pizza:

Gráfico de funil

O próximo gráfico que vamos adicionar no relatório é um gráfico mostrando as “top funcionalidades“, ou seja, as funcionalidades mais utilizadas no sistema. Não existe gráfico melhor para exibir esse tipo de informação do que o gráfico de funil.

Para criarmos um gráfico de funil, escolhemos o tipo:

E adicionamos o campo “Funcionalidade” nas duas listas da aba “Dados“:

Gráfico de linha

Em seguida, vamos criar um gráfico de linhas mostrando a carga do sistema, ou seja, um gráfico que mostra a utilização do sistema por dia:

Gráfico de barras empilhadas

Por fim, vamos adicionar um gráfico mostrando o nível de utilização das funcionalidades por usuário. Para essa informação, utilizaremos um gráfico de barras empilhadas:

Nesse caso, temos que adicionar as colunas “Funcionalidade” e “Usuario” na lista superior e qualquer uma dessas duas colunas na lista inferior:

Exibindo o relatório

Agora que já temos o nosso relatório com os gráficos propriamente ajustados, vamos configurar o nosso formulário para exibi-lo. Para isso, vamos adicionar um controle visualizador do Crystal Reports e vamos escolher o relatório que acabamos de criar:

Feito isso, vamos até o code-behind do formulário para criarmos uma lista de logs fictícios. Essa lista servirá como fonte de dados para o relatório:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();

            var listaLog = new List<LogFuncionalidade>();
            for (int contador = 0; contador < 200; contador++)
                listaLog.Add(LogFuncionalidade.GerarExemplo());
            RelatorioUtilizacaoSistema1.SetDataSource(listaLog);
            crystalReportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim ListaLog As New List(Of LogFuncionalidade)
        For contador As Integer = 0 To 200
            ListaLog.Add(LogFuncionalidade.GerarExemplo())
        Next
        RelatorioUtilizacaoSistema1.SetDataSource(ListaLog)
        CrystalReportViewer1.RefreshReport()
    End Sub

Pronto! Execute o projeto e veja o resultado:

Nota: não esqueça de configurar o elemento “useLegacyV2RuntimeActivationPolicy” como “true” no arquivo app.config. Caso contrário você receberá um erro ao executar o projeto.

Conclusão

Como você conferiu neste artigo, não é difícil adicionarmos gráficos nos nossos relatórios do Crystal Reports. Da mesma maneira que o Report Viewer, o Crystal Reports suporta os mais diversos tipos de gráficos. Porém, na minha opinião, os gráficos do Report Viewer são mais bonitos do que os gráficos do Crystal Reports. Portanto, caso os seus relatórios contenham muitos gráficos, eu recomendo que você considere a utilização do Report Viewer.

Você já trabalhou com gráficos no Crystal Reports? Se sim, quais tipos de gráficos você utilizou? Encontrou dificuldades no processo? Fico aguardando as suas observações na caixa de comentários logo abaixo.

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/business-risk-luck-chart-arrow-163501/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Trabalhando com gráficos no Crystal Reports appeared first on André Alves de Lima.


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

$
0
0

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

The post Juntando dois relatórios do Report Viewer em um só appeared first on André Alves de Lima.

Utilizando o Crystal Reports com MVVM no WPF

$
0
0

Sempre que desenvolvemos um projeto de software minimamente complexo, é recomendado que pensemos com muito cuidado na sua arquitetura antes mesmo de começar a codificar. Essa recomendação é ainda maior quando trabalhamos com WPF, devido à sua poderosíssima estrutura de data-binding que, se não utilizada, faz com que perca todo o sentido a utilização do WPF na construção do projeto.

Sem sombra de dúvidas, a arquitetura mais utilizada em projetos WPF é o MVVM. Quando utilizamos esse tipo de arquitetura, toda a lógica para alimentar as janelas da nossa aplicação fica armazenada nas ViewModels. Dessa forma, faz todo sentido que o carregamento do relatório fique separado na ViewModel. Isso é justamente o que eu vou mostrar neste artigo: como utilizar o Crystal Reports com MVVM no WPF.

Criando o projeto de exemplo

Antes de tudo, vamos começar criando um projeto do tipo “WPF Application“. Nesse projeto, vamos adicionar um relatório do Crystal Reports que será extremamente simples, contendo somente uma caixa de texto. Vamos dar o nome de “RelatorioTeste” para esse relatório:

Agora, vamos adicionar uma nova classe neste projeto, que servirá como ViewModel para a nossa janela. Dê o nome de “MainViewModel” para essa nova classe. O conteúdo dela também será muito simples: teremos uma propriedade do tipo “ReportDocument” e, no construtor da classe, setaremos essa propriedade utilizando uma nova instância do nosso “RelatorioTeste“:

    // C#
    public class MainViewModel
    {
        public CrystalDecisions.CrystalReports.Engine.ReportDocument Report { get; set; }
        
        public MainViewModel()
        {
            Report = new RelatorioTeste();
        }
    }
' VB.NET
Public Class MainViewModel
    Public Property Report As CrystalDecisions.CrystalReports.Engine.ReportDocument

    Public Sub New()
        Report = New RelatorioTeste()
    End Sub
End Class

Em seguida, temos que criar uma instância dessa classe dentro dos static resources da nossa aplicação, de forma que consigamos utilizar essa ViewModel diretamente via XAML na nossa janela. Para isso, temos que abrir o arquivo App.xaml (ou Application.xaml), adicionamos a declaração do namespace “local” apontando para o namespace geral do nosso projeto e incluímos a linha dentro da tag “Application.Resources” declarando uma instância da MainViewModel:

<Application x:Class="CrystalWPFMVVM.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:CrystalWPFMVVM"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <local:MainViewModel x:Key="MainViewModel" />
    </Application.Resources>
</Application>

Atenção: se a sua ViewModel estiver localizada em outro namespace (ou até mesmo em um outro assembly), você deve declarar o namespace na Application e utilizá-lo na hora de declarar a instância da ViewModel. No caso do exemplo deste artigo, a ViewModel está na raiz do mesmo projeto, portanto, declaramos e utilizamos o namespace “local“.

Por fim, vamos agora até a nossa janela, onde temos que setar o DataContext apontando para a instância de “MainViewModel” que acabamos de declarar na Application. Além disso, vamos arrastar um controle do Crystal Reports para dentro do grid e vamos dar o nome de “CrystalReportsViewer” para ele:

<Window xmlns:Viewer="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer"  x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    DataContext="{StaticResource MainViewModel}">
    <Grid>  
        <Viewer:CrystalReportsViewer x:Name="CrystalReportsViewer"/>
    </Grid>
</Window>

Nota: se você não está encontrando o controle do Crystal Reports na caixa de ferramentas do seu projeto WPF, é porque você tem que adicioná-lo manualmente. Confira a explicação detalhada desse processo no artigo: Trabalhando com o Crystal Reports no WPF.

Setando o ReportSource no code-behind da janela

Agora que já temos o DataContext da nossa janela apontando para uma instância de “MainViewModel“, como é que podemos configurar o ReportSource do controle do Crystal Reports utilizando a propriedade “Report” da nossa ViewModel? A primeira opção é configurarmos através do code-behind. Para isso, vamos até o code-behind e setamos a propriedade fazendo um cast de DataContext para MainViewModel para termos acesso à propriedade “Report“:

        // C#
        public MainWindow()
        {
            InitializeComponent();
            CrystalReportsViewer.ViewerCore.ReportSource = ((MainViewModel)DataContext).Report;
        }
' VB.NET
Class MainWindow 
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        CrystalReportsViewer.ViewerCore.ReportSource = DirectCast(DataContext, MainViewModel).Report
    End Sub
End Class

Execute o projeto e veja que o relatório será carregado com sucesso:

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.

Setando o ReportSource diretamente no XAML

OK, configurar o ReportSource no code-behind funciona, mas, você sabe muito bem que a maioria dos programadores que trabalham com WPF é aficionado por fazer tudo direto no XAML. Tem como fazer isso nesse caso? Tem! Só precisamos criar uma dependency property. Eu encontrei essa alternativa em uma thread do fórum do Crystal Reports no site da SAP: Binding Report Source on WPF Crystal Report Viewer Solution.

Basicamente, temos que adicionar uma nova classe estática dentro do nosso projeto, dando o nome de “ReportSourceBehaviour“. Dentro dessa classe, declaramos a dependency property e a sua implementação:

    // C#
    public static class ReportSourceBehaviour
    {
        public static readonly System.Windows.DependencyProperty ReportSourceProperty =
            System.Windows.DependencyProperty.RegisterAttached(
                "ReportSource",
                typeof(object),
                typeof(ReportSourceBehaviour),
                new System.Windows.PropertyMetadata(ReportSourceChanged));

        private static void ReportSourceChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            var crviewer = d as SAPBusinessObjects.WPF.Viewer.CrystalReportsViewer;
            if (crviewer != null)
            {
                crviewer.ViewerCore.ReportSource = e.NewValue;
            }
        }

        public static void SetReportSource(System.Windows.DependencyObject target, object value)
        {
            target.SetValue(ReportSourceProperty, value);
        }

        public static object GetReportSource(System.Windows.DependencyObject target)
        {
            return target.GetValue(ReportSourceProperty);
        }
    }
' VB.NET
Public NotInheritable Class ReportSourceBehaviour
    Public Shared ReadOnly ReportSourceProperty As System.Windows.DependencyProperty = _
        System.Windows.DependencyProperty.RegisterAttached("ReportSource", GetType(Object), GetType(ReportSourceBehaviour), New System.Windows.PropertyMetadata(AddressOf ReportSourceChanged))

    Private Shared Sub ReportSourceChanged(d As System.Windows.DependencyObject, e As System.Windows.DependencyPropertyChangedEventArgs)
        Dim crviewer = TryCast(d, SAPBusinessObjects.WPF.Viewer.CrystalReportsViewer)
        If crviewer IsNot Nothing Then
            crviewer.ViewerCore.ReportSource = e.NewValue
        End If
    End Sub

    Public Shared Sub SetReportSource(target As System.Windows.DependencyObject, value As Object)
        target.SetValue(ReportSourceProperty, value)
    End Sub

    Public Shared Function GetReportSource(target As System.Windows.DependencyObject) As Object
        Return target.GetValue(ReportSourceProperty)
    End Function
End Class

Feito isso, podemos voltar ao nosso XAML e fazemos o binding direto utilizando a dependency property:

<Window xmlns:Viewer="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer"  x:Class="CrystalWPFMVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CrystalWPFMVVM"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{StaticResource MainViewModel}">
    <Grid>
        <Viewer:CrystalReportsViewer x:Name="CrystalReportsViewer"
                                     local:ReportSourceBehaviour.ReportSource="{Binding Path=DataContext.Report, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=FrameworkElement}}"/>
    </Grid>
</Window>

Nota: não esqueça de declarar o namespace “local” apontando para o namespace raiz do projeto. Caso você tenha criado a classe “ReportSourceBehaviour” em algum outro lugar do projeto (ou até mesmo em um assembly diferente), você precisa declarar o namespace na janela e utilizá-lo (ao invés de utilizar o namespace “local“).

Concluindo

Utilizar o Crystal Reports com MVVM no WPF não só é possível como é muito simples. Neste artigo você conferiu como declarar o relatório na ViewModel e como utilizar o relatório declarado na ViewModel dentro das janelas da aplicação. Isso pode ser feito tanto via code-behind como diretamente no XAML.

E você, trabalha com o Crystal Reports no WPF? Utiliza a arquitetura MVVM? Se você utiliza, como é que você faz o carregamento dos relatórios? Do mesmo jeito que eu mostrei aqui no artigo? Se você não utiliza, qual é o motivo para ter escolhido trabalhar sem o MVVM nesse caso? De qualquer forma, deixe as suas observações na caixa de comentários logo abaixo.

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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Utilizando o Crystal Reports com MVVM no WPF appeared first on André Alves de Lima.

Atualizando um relatório do Report Viewer de tempos em tempos

$
0
0

Imagine que você tenha um relatório de dashboard no seu sistema e que você queira que esse relatório seja automaticamente atualizado de tempos em tempos. Pode ser que esse relatório fique 100% do tempo aberto em uma tela separada e que ele seja crucial para detectar irregularidades no processo. E aí, será que é possível atualizarmos um relatório do Report Viewer de tempos em tempos? É sim! Só precisamos utilizar um Timer. E isso é o que eu vou mostrar para você no artigo de hoje.

Criando o projeto de exemplo

Primeiramente, vamos criar um projeto para demonstrarmos essa funcionalidade. Para facilitar, criaremos um projeto do tipo “Windows Forms Application“, porém, saiba que podemos utilizar o Report Viewer no WPF, no WebForms e até mesmo no MVC. A mesma ideia que eu vou apresentar neste artigo para o Windows Forms se aplicaria para qualquer plataforma que você escolher.

Uma vez criado o projeto, vamos adicionar um novo DataSet tipado que servirá de fonte de dados para o relatório que iremos criar. Dê o nome de “DataSetDashboard” para esse novo DataSet e, dentro dele, adicione uma DataTable chamada “Dashboard“, contendo duas colunas (“NomeProduto” e “ValorVendido” – sendo que a coluna “ValorVendido” deve ser do tipo “Decimal“):

Em seguida, vamos criar um relatório muito simples com um gráfico de vendas por produto. Dê o nome de “RelatorioDashboard” para o novo relatório, adicione um gráfico e configure-o de forma que ele fique parecido com a imagem abaixo:

Nota: não vou entrar em detalhes sobre a criação de gráficos neste artigo. Se você tiver dúvidas, confira o artigo super-detalhado que eu escrevi uns tempos atrás sobre a criação de gráficos nos relatórios do Report Viewer.

Com o relatório criado, agora chegou a hora de exibi-lo no formulário. Para isso, arraste um controle do Report Viewer para dentro do formulário e configure-o para que ele exiba o relatório que acabamos de criar. Em seguida, no code-behind, vamos criar um método para adicionarmos informações aleatórias no DataSet do relatório:

        // C#
        Random _rnd = new Random();
        private void AdicionarInformacaoAleatoria()
        {
            var produtos = new string[] { "Produto 1", "Produto 2", "Produto 3" };
            var produto = produtos[_rnd.Next(3)];
            var valor = (decimal)_rnd.Next(10000) / (decimal)10;
            DataSetDashboard.Dashboard.AddDashboardRow(produto, valor);
        }
    ' VB.NET
    Private Rnd As New Random
    Private Sub AdicionarInformacaoAleatoria()
        Dim Produtos = New String() {"Produto 1", "Produto 2", "Produto 3"}
        Dim Produto = Produtos(Rnd.Next(3))
        Dim Valor = CDec(Rnd.Next(10000)) / CDec(10)
        DataSetDashboard.Dashboard.AddDashboardRow(Produto, Valor)
    End Sub

Obviamente, esse método não seria necessário em um sistema “de verdade“. Nesse caso, você teria que carregar os dados do banco para dentro do DataSet. De qualquer forma, vamos utilizar esse método de geração de dados aleatórios para termos um conjunto de dados de exemplo neste artigo.

Em seguida, vamos chamar esse método quatro vezes no construtor do formulário para gerarmos alguns dados que serão exibidos pelo relatório:

        // C#
        public FormDashboard()
        {
            InitializeComponent();

            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();
        }
    ' VB.NET
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()
    End Sub

Execute o projeto e veja que um gráfico com dados aleatórios será exibido:

Atualizando o relatório de tempos em tempos

Agora que já temos o nosso relatório criado e sendo exibido no formulário, vamos conferir como podemos atualizá-lo de tempos em tempos. Por questões didáticas, vamos fazer uma atualização a cada um segundo. Pelos testes que eu fiz, o controle do Report Viewer não apresenta problemas de memória mesmo se atualizado em intervalos muito pequenos. Porém, eu recomendo que você evite utilizar intervalos muito baixos para não prejudicar a experiência do usuário (afinal de contas, uma tela piscando a cada segundo para atualizar os dados do relatório não é nem um pouco amigável.

Para atualizarmos o relatório de tempos em tempos, temos que utilizar um timer. Vamos declarar o timer no nível do formulário e vamos configurá-lo diretamente no construtor, setando o intervalo desejado (mil milisegundos, ou seja, um segundo) e ajustando o hook para o evento “Tick“:

        // C#
        Timer _timer = new Timer();

        public FormDashboard()
        {
            InitializeComponent();

            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();
            AdicionarInformacaoAleatoria();

            _timer.Interval = 1000;
            _timer.Tick += Timer_Tick;
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            
        }
    ' VB.NET
    Private Timer As Timer

    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()
        AdicionarInformacaoAleatoria()

        Timer.Interval = 1000
        AddHandler Timer.Tick, AddressOf Timer_Tick
    End Sub

    Private Sub Timer_Tick(sender As Object, e As EventArgs)

    End Sub

Dentro do handler do evento “Tick“, teríamos que colocar o código para recarregar o DataSet e atualizar o relatório. No nosso exemplo, para conseguirmos sentir que os dados estão realmente sendo alterados, nós vamos adicionar mais um dado aleatório no DataSet a cada tick do Timer:

        // C#
        private void Timer_Tick(object sender, EventArgs e)
        {
            AdicionarInformacaoAleatoria();
            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub Timer_Tick(sender As Object, e As EventArgs)
        AdicionarInformacaoAleatoria()
        Me.ReportViewer1.RefreshReport()
    End Sub

Feito isso, a única coisa que está faltando fazer é iniciarmos o Timer através do seu método “Start“. Faremos isso no evento “Load” do formulário, logo após o primeiro “Refresh” do relatório:

        // C#
        private void FormDashboard_Load(object sender, EventArgs e)
        {
            this.reportViewer1.RefreshReport();
            _timer.Start();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.ReportViewer1.RefreshReport()
        Timer.Start()
    End Sub

Execute o projeto e veja só que legal fica o relatórios sendo atualizado a cada segundo:

Escondendo a barra de ferramentas

Muito provavelmente esse tipo de atualização automática só será útil para relatórios de dashboard, que serão exibidos em uma tela o tempo todo. Nesses casos, não faz muito sentido que a barra de ferramentas do Report Viewer fique visível. Para ocultá-la, basta configurarmos a propriedade “ShowToolBar” como “False“:

Finalizando o temporizador quando o formulário é fechado

Uma coisa importante que não devemos esquecer é finalizarmos o Timer no momento do fechamento do formulário. Caso contrário, pode ser que o Timer dispare quando o formulário já está destruído, causando uma exception na aplicação. Para isso, vamos fazer um “override” do método “OnClosing” do formulário e vamos finalizar o Timer através do seu método “Stop“:

        // C#
        protected override void OnClosing(CancelEventArgs e)
        {
            _timer.Stop();
            base.OnClosing(e);
        }
    ' VB.NET
    Protected Overrides Sub OnClosing(e As CancelEventArgs)
        Timer.Stop()
        MyBase.OnClosing(e)
    End Sub

Concluindo

A atualização automática de relatórios do Report Viewer de tempos em tempos pode ser útil em diversos cenários. O mais comum deles é quando queremos deixar um relatório de dashboard rodando em uma tela separada. No artigo de hoje você viu como utilizar um Timer para implementar essa atualização automática.

Você já precisou deixar um relatório rodando em uma tela dedicada? Caso positivo, você implementou algum mecanismo de atualização automática? Como você fez para atualizar o relatório de tempos em tempos? Deixe os detalhes na caixa de comentários abaixo.

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/stopwatch-time-clock-1082661/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Atualizando um relatório do Report Viewer de tempos em tempos appeared first on André Alves de Lima.

Imprimindo conteúdo do DataGridView no Crystal Reports

$
0
0

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/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Imprimindo conteúdo do DataGridView no Crystal Reports appeared first on André Alves de Lima.

Expondo PDFs gerados pelo Report Viewer com Web API

$
0
0

A cada dia que passa, aumenta a necessidade de criarmos diferentes versões dos nossos aplicativos, cada uma voltada para uma plataforma diferente. No mundo cada vez mais conectado de hoje em dia, não é raro termos o requisito que a nossa aplicação funcione em dispositivos móveis, além do desktop (ou web). Mas, e se a nossa aplicação utiliza o Report Viewer para gerar relatórios? Como fazer com que o mecanismo de geração de relatórios seja implementado somente em um lugar? Uma das opções é expormos PDFs gerados pelo Report Viewer com Web API. E é justamente isso que você vai conferir no artigo de hoje.

Criando o projeto de Web API

A ideia de criarmos uma Web API para gerarmos PDFs do relatório faz com que toda a lógica de geração dos relatórios seja separada neste projeto de Web API. Dessa forma, não temos repetição de código, além de tornar os relatórios compatíveis com todas as plataformas, uma vez que conseguimos exibir PDFs em praticamente qualquer lugar.

Vamos começar criando um novo projeto do tipo “ASP.NET Web Application” e escolhendo o template da “Web API“. Para facilitar o processo, desabilitei a autenticação e a hospedagem no Azure:

Criando o relatório

Com o projeto criado, vamos adicionar um novo DataSet dentro da pasta “Models“, que servirá de fonte de dados para o relatório. Vamos dar o nome de “DataSetFuncionario” para esse novo DataSet e, dentro dele, vamos criar uma nova DataTable chamada “Funcionario“:

Nota: não é obrigatório o uso de DataSets com o Report Viewer. Se você já tiver o arquivo .rdlc gerado utilizando um outro tipo de fonte de dados, você pode utilizá-lo sem problema na sua Web API. Por exemplo, é possível criarmos os relatórios .rdlc utilizando as classes de domínio do nosso projeto, como mostrei neste artigo sobre o Report Viewer com o Entity Framework.

Agora que já temos o DataSet criado, vamos adicionar uma nova pasta ao nosso projeto. Daremos o nome de “Reports” para essa pasta, e é dentro dela que armazenaremos os nossos relatórios:

Uma vez criada a pasta “Reports“, adicione um novo item do tipo “Report” dentro dessa pasta, dando o nome de “ReportFuncionario“:

Feito isso, adicione um novo DataSet no relatório, escolhendo o DataSet que criamos anteriormente e a DataTable “Funcionario“:

Em seguida, adicione uma tabela e arraste os campos do DataSet para dentro das colunas da tabela, de forma que o layout fique parecido com a imagem abaixo:

Expondo o PDF do relatório

Com o relatório criado, vamos partir para a criação do controller que “servirá” o PDF do relatório. Porém, antes de criarmos um novo controller, precisamos adicionar no nosso projeto a referência à dll do Report Viewer, que fica dentro da categoria “Extensions” da tela de adição de referências:

Agora que já temos a referência para a dll do Report Viewer, vamos criar um novo controller dentro da pasta “Controllers“. Escolha o template “Web API 2 Controller – Empty” (uma vez que nós só criaremos manualmente o método “Get” dentro desse controller) e escolha o nome de “RelatorioController“:

O método para gerarmos o PDF do relatório é muito simples. Primeiramente, precisamos criar o DataSet e uma instância de “LocalReport” passando o DataSet criado. Em seguida, utilizamos o método “Render” para gerarmos o PDF, resultando em um array de bytes. Por fim, retornamos o array de bytes como conteúdo de uma HttpResponseMessage:

    // C#
    public class RelatorioController : ApiController
    {
        public HttpResponseMessage Get()
        {
            var dataSet = new Models.DataSetFuncionario();
            dataSet.Funcionario.AddFuncionarioRow("André", "Lima", DateTime.Now);
            dataSet.Funcionario.AddFuncionarioRow("Fulano", "de Tal", DateTime.Now);
            dataSet.Funcionario.AddFuncionarioRow("Beltrano", "da Silva", DateTime.Now);

            var report = new Microsoft.Reporting.WebForms.LocalReport();
            report.ReportPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Reports/ReportFuncionario.rdlc");
            report.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("DataSetFuncionario", (System.Data.DataTable)dataSet.Funcionario));
            report.Refresh();

            string mimeType = "";
            string encoding = "";
            string filenameExtension = "";
            string[] streams = null;
            Microsoft.Reporting.WebForms.Warning[] warnings = null;
            byte[] bytes = report.Render("PDF", null, out mimeType, out encoding, out filenameExtension, out streams, out warnings);

            HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
            result.Content = new ByteArrayContent(bytes);
            result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mimeType);
            return result;
        }
    }
    ' VB.NET
    Public Class RelatorioController
        Inherits ApiController

        Public Function GetValues() As Http.HttpResponseMessage
            Dim DataSet As New DataSetFuncionario()
            DataSet.Funcionario.AddFuncionarioRow("André", "Lima", DateTime.Now)
            DataSet.Funcionario.AddFuncionarioRow("Fulano", "de Tal", DateTime.Now)
            DataSet.Funcionario.AddFuncionarioRow("Beltrano", "da Silva", DateTime.Now)

            Dim Report As New Microsoft.Reporting.WebForms.LocalReport()
            Report.ReportPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Reports/ReportFuncionario.rdlc")
            Report.DataSources.Add(New Microsoft.Reporting.WebForms.ReportDataSource("DataSetFuncionario", DirectCast(DataSet.Funcionario, System.Data.DataTable)))
            Report.Refresh()

            Dim MimeType As String = ""
            Dim Encoding As String = ""
            Dim FilenameExtension As String = ""
            Dim Streams As String() = Nothing
            Dim Warnings As Microsoft.Reporting.WebForms.Warning() = Nothing
            Dim Bytes As Byte() = Report.Render("PDF", Nothing, MimeType, Encoding, FilenameExtension, Streams, Warnings)

            Dim Result As Http.HttpResponseMessage = New Http.HttpResponseMessage(HttpStatusCode.OK)
            Result.Content = New Http.ByteArrayContent(Bytes)
            Result.Content.Headers.ContentType = New System.Net.Http.Headers.MediaTypeHeaderValue(MimeType)
            Return Result
        End Function
    End Class

Execute a aplicação e, na janela do browser, adicione o final “/api/Relatorio” na URL da API para ver o PDF sendo gerado:

Possível melhoria

No exemplo que vimos até agora, criamos um controller para exibirmos um relatório. Obviamente, em um projeto com vários relatórios, isso se transformaria rapidamente em um pesadelo. Uma possível melhoria que poderíamos implementar nesse caso é termos somente um controller responsável pela geração dos PDFs e esse controller receberia o nome do relatório a ser gerado. Isso poderia ser feito adicionando um parâmetro string no método “Get” da API. Dessa forma, poderíamos hipoteticamente obter, por exemplo, o relatório de funcionários através da URL “/api/Relatorios/Funcionarios” e o relatório de clientes através da URL “/api/Relatorios/Clientes“:

        // C#
        public HttpResponseMessage Get(string id)
        {
            switch (id.ToUpper())
            {
                case "FUNCIONARIOS":
                    return GetRelatorioFuncionarios();
                case "CLIENTES":
                    return GetRelatorioClientes();
                default:
                    return null;
            }
        }
    ' VB.NET
    Public Function GetValue(ByVal id As String) As Http.HttpResponseMessage
        Select Case id.ToUpper()
            Case "FUNCIONARIOS"
                Return GetRelatorioFuncionarios()
            Case "CLIENTES"
                Return GetRelatorioClientes()
            Case Else
                Return Nothing
        End Select
    End Function

Acessando a Web API em outros projetos

Uma vez tendo a API em execução, podemos executá-la de qualquer tipo de aplicativo que suporte requisições HTTP. Por exemplo, em uma Console Application, poderíamos recuperar o PDF e exibi-lo na ferramenta padrão através deste código:

            // C#
            var request = System.Net.WebRequest.Create("http://localhost:34321/api/Relatorio");
            var response = request.GetResponse();
            using (var fileStream = System.IO.File.Create("arquivo.pdf"))
            {
                response.GetResponseStream().CopyTo(fileStream);
            }
            System.Diagnostics.Process.Start("arquivo.pdf");
        ' VB.NET
        Dim Request = System.Net.WebRequest.Create("http://localhost:34321/api/Relatorio")
        Dim Response = Request.GetResponse()
        Using fileStream = System.IO.File.Create("arquivo.pdf")
            Response.GetResponseStream().CopyTo(fileStream)
        End Using
        System.Diagnostics.Process.Start("arquivo.pdf")

Concluindo

A criação de Web APIs para expormos PDFs dos nossos relatórios do Report Viewer é um artifício que podemos utilizar para termos acesso aos nossos relatórios através de aplicações que não suportem o controle do Report Viewer (como aplicações mobile). Neste artigo você aprendeu a montar rapidamente uma Web API expondo o PDF de um relatório do Report Viewer. Vimos também como podemos acessar essa Web API através de uma Console Application, onde abrimos o PDF gerado pela API.

E você, já teve a necessidade de gerar um relatório do Report Viewer numa plataforma não suportada? Como você acabou resolvendo essa necessidade? Através de uma Web API, algum outro tipo de serviço ou alguma outra alternativa? Conte-nos mais detalhes 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/document-agreement-documents-sign-428331/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Expondo PDFs gerados pelo Report Viewer com Web API appeared first on André Alves de Lima.

Formatação condicional no Crystal Reports

$
0
0

O que seria um relatório sem regras que alterem a sua formatação dependendo de condições de negócios? Muito provavelmente seria um relatório bem “chato“, somente listando alguns valores e deixando 100% da sua interpretação a cargo do usuário. Se você está acostumado a entregar os seus relatórios dessa maneira “sem graça“, já passou da hora de mudar essa realidade. Uns tempos atrás eu mostrei como trabalhar com expressões no Report Viewer e hoje é a vez do Crystal Reports. No artigo de hoje, confira como adicionar formatação condicional no Crystal Reports.

Criando o projeto e a classe de domínio

Para entendermos a formatação condicional no Crystal Reports, temos que primeiramente criar um relatório. Para simplificar o entendimento, o nosso relatório será uma simples listagem, porém, com algumas formatações dependendo de regras de negócios.

Primeiramente, vamos começar criando um novo projeto do tipo “Windows Forms Application“. No final das contas, não importa a plataforma que você escolha, uma vez que trabalharemos a formatação no próprio relatório (arquivo .rpt), que seria igual para qualquer plataforma que você estiver trabalhando. Eu só escolhi Windows Forms por ser mais fácil e por ser a plataforma mais utilizada.

Dentro do projeto, vamos criar uma pequena classe de domínio chamada “Produto“. Essa classe terá algumas propriedades, que serão todas posteriormente listadas no relatório:

    // C#
    public class Produto
    {
        public int ProdutoId { get; set; }
        public string Nome { get; set; }
        public decimal Preco { get; set; }
        public int EstoqueMinimo { get; set; }
        public int EstoqueAtual { get; set; }
        public bool Descontinuado { get; set; }
    }
' VB.NET
Public Class Produto
    Public Property ProdutoId As Integer
    Public Property Nome As String
    Public Property Preco As Decimal
    Public Property EstoqueMinimo As Integer
    Public Property EstoqueAtual As Integer
    Public Property Descontinuado As Boolean
End Class

Criando o esqueleto do relatório

Com a classe de domínio criada, vamos partir para a criação do esqueleto do relatório. Para isso, adicione no projeto um novo item do tipo “Crystal Reports“. Esse tipo de item fica dentro da categoria “Reporting“. Dê o nome de “RelatorioProdutos” para este novo item:

A partir daqui, temos duas opções: ou criamos o relatório em branco ou utilizamos o assistente. Neste artigo eu vou prosseguir com o relatório em branco, mas, se você quiser seguir com o assistente, não fará diferença alguma.

Uma vez criado o relatório, temos que configurar a fonte de dados do relatório. Para isso, vamos até a janela de “Field Explorer“, clicamos com o botão direito em “Database Fields” e escolhemos a opção “Database Expert“:

Em seguida, temos que encontrar a nossa classe “Produto” para jogarmos para o lado “Selected Tables“. Encontramos essa classe dentro da categoria “.NET Objects“:

Por fim, vamos arrastar os campos da janela “Field Explorer” para dentro da área de detalhes do relatório e vamos adicionar um título ao relatório, de forma que ele fique parecido com a imagem abaixo:

Exibindo o relatório

Agora que já temos o nosso relatório em mãos, vamos exibir esse relatório no nosso formulário. Para isso, temos que arrastar um controle do Crystal Reports para dentro do formulário e, em seguida, temos que escolher o relatório que acabamos de criar:

Feito isso, vamos adicionar um pouquinho de código no code-behind do formulário para criarmos e passarmos uma lista de Produtos para o relatório. Faremos isso com um laço “for” e alguns dados aleatórios. Obviamente, no seu projeto “de verdade“, você teria que recuperar os dados do banco:

        // C#
        public Form1()
        {
            InitializeComponent();

            var rand = new Random();
            var lista = new List<Produto>();
            for (int contador = 1; contador <= 100; contador++)
            {
                lista.Add(new Produto()
                {
                    ProdutoId = contador,
                    Nome = "Produto " + contador,
                    Preco = Convert.ToDecimal(rand.NextDouble() * 100),
                    EstoqueMinimo = rand.Next(1, 1000),
                    EstoqueAtual = rand.Next(1, 1000),
                    Descontinuado = Convert.ToBoolean(rand.Next(-1, 1))
                });
            }
            RelatorioProdutos1.SetDataSource(lista);
            RelatorioProdutos1.Refresh();
        }
    ' VB.NET
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Rand As Random = New Random()
        Dim Lista As List(Of Produto) = New List(Of Produto)
        For Contador As Integer = 1 To 100
            Lista.Add(New Produto() With
                {
                    .ProdutoId = Contador,
                    .Nome = "Produto " & Contador,
                    .Preco = Convert.ToDecimal(Rand.NextDouble() * 100),
                    .EstoqueMinimo = Rand.Next(1, 1000),
                    .EstoqueAtual = Rand.Next(1, 1000),
                    .Descontinuado = Convert.ToBoolean(Rand.Next(-1, 1))
                })
        Next
        RelatorioProdutos1.SetDataSource(Lista)
        RelatorioProdutos1.Refresh()
    End Sub

Execute o projeto e veja o resultado do relatório sem a formatação condicional:

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.

Adicionando formatação condicional

E agora, André? Como é que eu adiciono formatação condicional nesse relatório? Não se preocupe. É justamente isso que eu vou mostrar para você a partir daqui. Que tal alterarmos a cor da fonte de “Estoque mínimo” para vermelho caso ele seja menor que o “Estoque atual“?

O Crystal Reports é extremamente flexível no que diz respeito à formatação condicional. Diferente do Report Viewer, ele suporta a criação de fórmulas para praticamente qualquer propriedade do relatório. Todo lugar onde você encontrar um botão com um símbolo “x2” quer dizer que você pode adicionar uma fórmula para aquela propriedade.

Por exemplo, para adicionarmos a formatação do “Estoque mínimo“, temos que abrir a formatação do controle clicando com o botão direito e escolhendo a opção “Format Object“:

Em seguida, temos que ir até a aba “Font“. Veja só os botões “x2” que eu mencionei anteriormente:

No nosso caso, como queremos adicionar uma lógica referente à cor da célula, temos que clicar no botão “x2” da propriedade “Color“. Quando clicamos no botão, a janela de edição de fórmula do Crystal Reports será aberta. Dentro dessa janela, temos que colocar a expressão que deverá retornar “vermelho” caso o “Estoque atual” seja menor que o “Estoque mínimo” ou preto caso contrário.

As fórmulas do Crystal Reports podem ser escritas de duas maneiras: utilizando a sintaxe do próprio Crystal Reports (que é tipo um Pascal misturado com Basic e C) ou utilizando a sintaxe Basic. Eu nunca vi ninguém utilizar a sintaxe Basic em relatórios do Crystal Reports, mas, é totalmente possível. Essa opção pode ser bem útil caso você esteja acostumado a desenvolver relatórios no Report Viewer ou caso você trabalhe com VB.NET.

Para implementarmos essa lógica da cor, utilizamos a seguinte fórmula:

if ({CrystalFormatacaoCustomizada_Produto.EstoqueAtual} < {CrystalFormatacaoCustomizada_Produto.EstoqueMinimo}) then
    crRed
else
    crBlack

Ou, caso você prefira trabalhar com a sintaxe em Basic, a fórmula seria esta:

formula = IIf({CrystalFormatacaoCustomizada_Produto.EstoqueAtual} < {CrystalFormatacaoCustomizada_Produto.EstoqueMinimo}, crRed, crBlack)

Para escolher a sintaxe da fórmula, utilize esse dropdown:

Dentro do mesmo relatório você pode ter algumas fórmulas com a sintaxe do Crystal Reports e outras fórmulas com sintaxe Basic.

Escondendo linhas baseando-se em uma condição

Agora que já vimos como trabalhar a formatação de textos baseando-se em uma fórmula, vamos ver como podemos esconder registros utilizando uma regra de negócio. No nosso exemplo, vamos supor que queiramos esconder os produtos descontinuados.

É possível adicionarmos lógicas de negócio para cada seção do relatório (cabeçalho do relatório, cabeçalho de página, detalhes, rodapés, etc). Para isso, basta clicarmos na seção desejada (no nosso caso, a seção de detalhes) e então, temos que clicar na opção “Section Expert“:

Note que, dentro da janela “Section Expert“, temos os famosos botões “x2” para as propriedades da seção:

No nosso caso, vamos adicionar uma fórmula na propriedade “Suppress“. A fórmula deverá retornar “true” caso o registro deva ser escondido, e “false” caso contrário. Dessa forma, para escondermos os produtos descontinuados, nós só temos que utilizar o campo “Descontinuado” da nossa tabela de produtos:

{CrystalFormatacaoCustomizada_Produto.Descontinuado}

Execute a aplicação e veja o resultado:

Como você pode notar, nos produtos em que “Estoque atual” é menor que “Estoque mínimo“, a célula correspondente ao “Estoque atual” está pintada de vermelho. Além disso, note que alguns produtos foram escondidos (Ids 3, 5, 6, etc). Esses produtos foram escondidos porque estão descontinuados.

Concluindo

O Crystal Reports é extremamente flexível no que diz respeito à adição de lógicas de negócio para as propriedades dos seus controles e seções. Nós podemos adicionar fórmulas em praticamente todas as propriedades de todos os controles. Essas fórmulas podem ser escritas tanto utilizando a sintaxe do próprio Crystal Reports quanto a sintaxe em Basic.

Neste artigo você conferiu como trabalhar com formatação condicional no Crystal Reports, adicionando uma fórmula na propriedade “Color” de uma célula, de forma que ela seja pintada de vermelho em algumas situações. Além disso, você aprendeu também a esconder registros do relatório baseando-se em uma fórmula.

E você, já utilizou as fórmulas do Crystal Reports? Qual foi a fórmula mais maluca que você já teve que implementar nos seus relatórios? Você costuma utilizar a sintaxe do Crystal Reports ou a sintaxe em Basic? Estamos todos curiosos para saber, então, conte-nos mais detalhes 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

Photo by Pixabay used under Creative Commons
https://pixabay.com/en/typography-geschtaltung-fonts-1069409/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Formatação condicional no Crystal Reports appeared first on André Alves de Lima.

Quebra de página condicional no Report Viewer

$
0
0

O conceito de quebra de páginas no desenvolvimento de relatórios é algo básico a ser dominado. É rara a situação em que um relatório ocupa somente uma página, principalmente quando falamos de relatórios mais complexos. O que pode ser algo muito básico pode se tornar um pesadelo para os desenvolvedores que utilizam o Report Viewer como ferramenta de relatórios. Isso porque não é tão simples e muito menos intuitivo criarmos uma quebra de página condicional no Report Viewer. Mas, não se preocupe. No artigo de hoje eu vou desmistificar esse tema para você.

Relatório sem quebra de páginas

Quando criamos um relatório no Report Viewer, por padrão, ele não tem quebra de página customizada. Ou seja, as informações serão impressas na primeira página e, somente quando não tiver mais espaço na primeira página é que o Report Viewer quebrará o relatório em uma segunda página, e assim por diante.

Para conseguirmos entender o conceito de quebra de páginas, vamos criar um relatório bem simples utilizando um projeto do tipo “Windows Forms Application“. Como eu costumo dizer, poderíamos utilizar outros tipos de projeto (WPF, Web Forms, MVC), mas, escolhi o Windows Forms por ser mais simples.

Vamos supor que nós temos um sistema que gerencia livros. O nosso relatório mostrará uma lista dos livros cadastrados no sistema. Dessa forma, a primeira coisa que temos que fazer é criarmos uma nova classe, chamada “Livro“. Mais para frente, o nosso relatório receberá uma lista de instâncias dessa classe, que será justamente a fonte de dados do relatório:

    // C#
    public class Livro
    {
        public int ID { get; set; }
        public string Nome { get; set; }
        public string GeneroLiterario { get; set; }
        public string TipoGeneroLiterario { get; set; }
    }
' VB.NET
Public Class Livro
    Public Property ID As Integer
    Public Property Nome As String
    Public Property GeneroLiterario As String
    Public Property TipoGeneroLiterario As String
End Class

Uma vez adicionada a classe, compile o projeto. Esse é um passo muito importante, uma vez que, caso esqueçamos de compilar o projeto antes de criarmos o relatório, o mecanismo do Report Viewer não reconhecerá essa classe.

Com o projeto compilado, vamos adicionar um novo relatório do Report Viewer, dando o nome de “RelatorioListaLivros” para esse novo relatório. Dentro do relatório, adicione uma nova Table. Quando adicionamos uma Table no relatório, o Report Viewer perguntará informações sobre a fonte de dados que deverá ser utilizada para alimentar essa Table. No nosso caso, vamos criar uma nova fonte de dados do tipo “Object Data Source“:

Na próxima tela, encontre a classe “Livro” e conclua o assistente:

Em seguida, dê o nome de “DataSetLivro” para o DataSet que será criado:

Por fim, arraste os campos do DataSet para dentro das colunas da Table, de forma que o relatório fique parecido com a imagem abaixo:

Nada demais, não é mesmo? Vamos exibir esse relatório em tempo de execução para conferirmos o resultado? Para isso, abra o designer do formulário e arraste um controle do Report Viewer para dentro dele. Na smart tag do controle, escolha o relatório que acabamos de criar e, no code-behind do formulário, vamos passar uma lista de livros como fonte de dados do relatório:

        // C#
        private void FormRelatorio_Load(object sender, EventArgs e)
        {
            var livros = new List<Livro>();
            livros.Add(new Livro() { ID = 1, Nome = "Ilíada", GeneroLiterario = "Epopéia", TipoGeneroLiterario = "Narrativo" });
            livros.Add(new Livro() { ID = 2, Nome = "A divina comédia", GeneroLiterario = "Epopéia", TipoGeneroLiterario = "Narrativo" });
            livros.Add(new Livro() { ID = 3, Nome = "Memórias póstumas de Brás Cubas", GeneroLiterario = "Romance", TipoGeneroLiterario = "Narrativo" });
            livros.Add(new Livro() { ID = 4, Nome = "Macunaíma", GeneroLiterario = "Romance", TipoGeneroLiterario = "Narrativo" });
            livros.Add(new Livro() { ID = 5, Nome = "Morte e vida Severina", GeneroLiterario = "Poesia", TipoGeneroLiterario = "Lírico" });
            livros.Add(new Livro() { ID = 6, Nome = "As vespas", GeneroLiterario = "Comédia", TipoGeneroLiterario = "Dramático" });
            livros.Add(new Livro() { ID = 7, Nome = "O misantropo", GeneroLiterario = "Comédia", TipoGeneroLiterario = "Dramático" });
            livros.Add(new Livro() { ID = 8, Nome = "Auto da compadecida", GeneroLiterario = "Drama", TipoGeneroLiterario = "Dramático" });
            this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetLivro", livros));

            this.reportViewer1.RefreshReport();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Livros As New List(Of Livro)
        Livros.Add(New Livro() With {.ID = 1, .Nome = "Ilíada", .GeneroLiterario = "Epopéia", .TipoGeneroLiterario = "Narrativo"})
        Livros.Add(New Livro() With {.ID = 2, .Nome = "A divina comédia", .GeneroLiterario = "Epopéia", .TipoGeneroLiterario = "Narrativo"})
        Livros.Add(New Livro() With {.ID = 3, .Nome = "Memórias póstumas de Brás Cubas", .GeneroLiterario = "Romance", .TipoGeneroLiterario = "Narrativo"})
        Livros.Add(New Livro() With {.ID = 4, .Nome = "Macunaíma", .GeneroLiterario = "Romance", .TipoGeneroLiterario = "Narrativo"})
        Livros.Add(New Livro() With {.ID = 5, .Nome = "Morte e vida Severina", .GeneroLiterario = "Poesia", .TipoGeneroLiterario = "Lírico"})
        Livros.Add(New Livro() With {.ID = 6, .Nome = "As vespas", .GeneroLiterario = "Comédia", .TipoGeneroLiterario = "Dramático"})
        Livros.Add(New Livro() With {.ID = 7, .Nome = "O misantropo", .GeneroLiterario = "Comédia", .TipoGeneroLiterario = "Dramático"})
        Livros.Add(New Livro() With {.ID = 8, .Nome = "Auto da compadecida", .GeneroLiterario = "Drama", .TipoGeneroLiterario = "Dramático"})
        Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetLivro", Livros))

        Me.ReportViewer1.RefreshReport()
    End Sub

Execute o projeto e veja o resultado:

Nota: me perdoe se os gêneros literários desse exemplo estiverem errados. Tudo o que eu lembro de literatura é o que eu estudei no colegial, lá por volta do ano 2000.

Agora você deve estar pensando: “OK, André, esse tipo de relatório eu estou cansado de fazer. Como é que eu faço a quebra de página baseada em uma regra de negócio? Não era isso que você ia mostrar nesse artigo?“.

Calma! Não se estresse. Acompanhe logo abaixo alguns tipos de quebra condicional que você pode adicionar nos seus relatórios do Report Viewer.

Quebrando página a cada N registros

O primeiro tipo de quebra condicional que eu quero mostrar neste artigo é a quebra de página a cada “N” registros. Tomando como base o nosso exemplo, vamos supor que nós queiramos mostrar somente quatro livros por página. Como é que nós fazemos isso?

Como o Report Viewer não suporta a definição de uma expressão para quebra de páginas, a alternativa é criarmos um agrupamento baseado na expressão desejada e, em seguida, configurarmos a quebra de página entre instâncias desse agrupamento. Pareceu complicado? Não se preocupe. Até que é bem simples, veja só.

Em primeiro lugar, vamos adicionar um agrupamento no relatório. Para isso, vamos até a janela de “Row Groups“, clicamos no dropdown dos detalhes e escolhemos a opção “Add Group => Parent Group“:

E é na próxima tela que mora todo o segredo. Na tela de criação de agrupamentos, temos que clicar no botão “fx” para configurarmos a expressão que será utilizada para fazer a quebra de página:

A expressão para quebrarmos a página a cada 4 registros seria a seguinte:

=Math.Ceiling(RowNumber(Nothing) / 4)

Note que estamos utilizando a função RowNumber passando “Nothing“, que retornará o número do registro considerando o DataSet como um todo. Se quiséssemos o número da linha considerando um agrupamento, teríamos que passar o nome do agrupamento como parâmetro para a função RowNumber.

Além disso, estamos utilizando também a função Math.Ceiling, que retorna o inteiro mais próximo acima do valor passado. Essa função é tipo um arredondamento forçado para cima. Dessa forma, na primeira linha, teremos RowNumber 1 dividido por 4, resultando em 0,25, arredondado para cima = 1. O resultado será o mesmo para as linhas de 1 até 4. A partir da linha 5, o resultado começa a ser 2 (5 / 4 = 1,25 => arredondado para cima = 2). Com isso, teremos um valor em comum entre as linhas que queremos agrupar.

Um pequeno detalhe é que, após a criação do grupo, o Report Viewer criará automaticamente uma coluna na tabela com o seu valor. Essa coluna pode ser excluída, porém, temos que tomar cuidado para excluirmos somente a coluna (e não o agrupamento):

Agora só falta configurarmos a quebra de página entre as instâncias do grupo e nós teremos o resultado esperado. Para configurarmos a quebra de página, temos que ir até as propriedades do agrupamento e, dentro da categoria “Page Breaks“, temos que marcar a opção “Between each instance of a group“:

Pronto! Tente executar o projeto e receba este belo erro de compilação:

A sort expression for tablix ‘Tablix1’ uses the RowNumber function. RowNumber cannot be used in sort expressions.

Esse erro acontece porque, quando criamos um agrupamento no Report Viewer, ele automaticamente configura a expressão correspondente ao agrupamento como a sua expressão de ordenação. Porém, nesse caso, como estamos utilizando a função RowNumber, nós não podemos utilizar essa expressão na ordenação do agrupamento. Dessa forma, temos que ir até as propriedades do grupo e, dentro da categoria “Sorting“, temos que excluir a expressão de ordenação:

Agora sim! Execute o projeto e veja o resultado:

Como você pode perceber, agora temos quatro registros por página. Obviamente, para o layout ficar completo, teríamos que mover a linha de cabeçalho para dentro do cabeçalho do grupo (para que a linha de cabeçalho seja repetida em todas as páginas).

Quebra de página baseada em uma expressão customizada

Da mesma forma que configuramos uma expressão para quebrarmos a página a cada “N” registros, nós podemos configurar qualquer expressão que quisermos para controlarmos a quebra de página. Por exemplo, digamos que nós precisemos quebrar o relatório em livros do tipo “Dramáticos” e “Não Dramáticos” (livros dramáticos em uma página e livros não dramáticos em outra página). Nesse caso, nós poderíamos seguir o mesmo processo, só que utilizando a seguinte expressão:

=IIf(Fields!TipoGeneroLiterario.Value = "Dramático", 1, 0)

E o resultado seria o seguinte:

Ativando ou desativando a quebra de página

Por fim, existe aquela famosa situação em que alguns clientes querem o relatório com a quebra de página e outros clientes querem o relatório sem a quebra de página. Como fazer nesse caso? Criar duas versões do relatório, uma com quebra e outra sem quebra? É claro que não! Nós podemos utilizar um parâmetro para controlar se a quebra de página está ativa ou não!

Para isso, primeiramente temos que criar um novo parâmetro no nosso relatório. Vamos chamar esse parâmetro de “AtivaQuebra“, configurando o tipo “Boolean” e a opção “Allow null value“:

Em seguida, temos que clicar no agrupamento (na janela “Row Groups“) e, na janela de propriedades, temos que navegar até a propriedade “Group => Page Break => Disabled” para configurarmos uma expressão para essa propriedade:

A expressão para essa propriedade deve ser a seguinte:

=Not Parameters!AtivaQuebra.Value

Após essa alteração, a quebra de páginas customizada só estará ativa caso nós passemos o valor “True” para esse parâmetro antes de exibirmos o relatório, caso contrário a quebra de páginas será ignorada. Isso pode ser feito no code-behind do formulário, antes da chamada de “RefreshReport“:

            // C#
            // Se comentarmos a linha abaixo, a quebra de páginas customizada será ignorada.
            this.reportViewer1.LocalReport.SetParameters(new Microsoft.Reporting.WinForms.ReportParameter("AtivaQuebra", "True"));
            this.reportViewer1.RefreshReport();
        ' VB.NET
        ' Se comentarmos a linha abaixo, a quebra de páginas customizada será ignorada.
        Me.ReportViewer1.LocalReport.SetParameters(New Microsoft.Reporting.WinForms.ReportParameter("AtivaQuebra", "True"))
        Me.ReportViewer1.RefreshReport()

Concluindo

Quebra de página condicional no Report Viewer é algo nada intuitivo, mas, totalmente possível de ser feito. Através de criação de grupos com expressões customizadas, podemos criar quebras de páginas com qualquer regra de negócio que desejarmos. Nesse artigo você conferiu como quebrar o relatório a cada “N” registros, como configurar a quebra de páginas com expressões customizadas e como fazer para ativar ou desativar a quebra de página através de parâmetros.

E você, já precisou configurar uma quebra de página condicional no Report Viewer? Qual era a situação em que você teve que criar essa quebra de página customizada? Funcionou direitinho através da criação de agrupamentos? Conte-nos mais detalhes 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/calculator-calculation-insurance-1044173/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Quebra de página condicional no Report Viewer appeared first on André Alves de Lima.


Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll

$
0
0

Um erro clássico que acontece ao tentarmos exibir um relatório do Crystal Reports em aplicações que utilizam o .NET Framework 4 ou superior é o “FileNotFoundException” relacionado ao arquivo “crdb_adoplus.dll“. Como é que esse erro pode ser resolvido? A maneira mais simples e mais utilizada é fazermos um pequeno ajuste no arquivo app.config da nossa aplicação. Porém, essa não é a única maneira. Existe uma outra alternativa que faz muito sentido em alguns casos. No artigo de hoje você verá essas duas maneiras de resolver o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll.

Entendendo o problema

Para entendermos esse problema, vamos criar um novo projeto do tipo “Windows Forms Application“. Dentro desse projeto, vamos adicionar um relatório do Crystal Reports em branco (vamos dar o nome de “Relatorio” para esse novo relatório que está sendo criado). Em seguida, no formulário, temos que arrastar um controle do Crystal Reports e selecionar o relatório que foi criado no passo anterior.

Ao executarmos essa aplicação, não recebemos erro algum:

Porém, vamos combinar que uma aplicação com um relatório em branco sem fonte de dados não tem nenhuma utilidade. Dessa forma, vamos criar uma classe muito simples que servirá de fonte de dados para o relatório. Essa classe se chamará “QualquerClasse” e terá somente uma propriedade (chamada “Propriedade“):

    // C#
    public class QualquerClasse
    {
        public int Propriedade { get; set; }
    }
' VB.NET
Public Class QualquerClasse
    Public Property Propriedade As Integer
End Class

Feito isso, no relatório, vamos até o Database Expert para arrastarmos essa classe para dentro do relatório:

A partir desse momento, ao executarmos novamente a nossa aplicação, o Crystal Reports mostrará a típica caixa de login que aparece quando não passamos a fonte de dados para o nosso relatório:

Isso faz todo o sentido, uma vez que temos uma tabela definida no nosso relatório e nós não estamos alimentando essa tabela ao exibirmos o relatório. OK, então vamos alimentar essa tabela com uma coleção de “QualquerClasse“. Essa coleção conterá somente uma instância dessa classe:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();
            Relatorio1.SetDataSource(new List<QualquerClasse>(new QualquerClasse[] { new QualquerClasse() { Propriedade = 1 } }));
        }
    ' VB.NET
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

E é aí que surge o problema. Nesse ponto, ao executarmos novamente a nossa aplicação, receberemos esse belo erro:

An unhandled exception of type ‘System.IO.FileNotFoundException’ occurred in mscorlib.dll
Additional information: Could not load file or assembly ‘file:///C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\dotnet1\crdb_adoplus.dll’ or one of its dependencies. The system cannot find the file specified.

Como mencionei no início do artigo, esse erro pode ser consertado de duas maneiras. Vamos conferir as duas opções?

Opção 1: useLegacyV2RuntimeActivationPolicy no app.config

A primeira opção é bem simples. Nós só temos que fazer um pequeno ajuste no arquivo app.config da nossa aplicação. Na tag “startup“, temos que definir o elemento “useLegacyV2RuntimeActivationPolicy” como “true“:

Se você quiser copiar e colar, segue o código da tag “startup” já com o ajuste desse elemento:

    <startup useLegacyV2RuntimeActivationPolicy="true"> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>

Execute novamente a aplicação e veja que o erro foi resolvido.

Opção 2: RuntimePolicyHelper

Em algumas situações, adicionarmos essa tag no arquivo app.config acaba sendo muito complicado. Isso acontece principalmente quando desmembramos a exibição dos relatórios em uma biblioteca (dll) separada. Nesse caso, seria o app.config da aplicação consumidora que deveria ser alterado, e isso é definitivamente algo que deve ser evitado ao disponibilizarmos uma biblioteca que pode ser consumida por várias aplicações consumidoras.

Na empresa onde eu trabalho nós tivemos exatamente esse problema. A lógica “genérica” de exibição de relatórios do Crystal Reports fica em uma dll separada, que é consumida por “N” aplicações. Nós queríamos evitar que o app.config de cada aplicação consumidora tivesse que ser alterado ao utilizar o Crystal Reports. Ao invés disso, nós queríamos fazer essa alteração do “legacy runtime” automaticamente quando a aplicação fosse executada.

Depois de pesquisar um pouquinho, encontramos uma classe “mágica que faz com que esse elemento seja adicionado em tempo de execução, evitando que o app.config tenha que ser alterado nas aplicações consumidoras. Vamos ver como essa classe funciona?

Primeiramente, vamos adicionar uma nova classe ao nosso projeto, dando o nome de “RuntimePolicyHelper“. Aqui vai o código dessa classe:

    // C#
    public static class RuntimePolicyHelper
    {
        public static bool LegacyV2RuntimeEnabledSuccessfully { get; private set; }

        static RuntimePolicyHelper()
        {
            ICLRRuntimeInfo clrRuntimeInfo =
                (ICLRRuntimeInfo)System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(
                    Guid.Empty,
                    typeof(ICLRRuntimeInfo).GUID);
            try
            {
                clrRuntimeInfo.BindAsLegacyV2Runtime();
                LegacyV2RuntimeEnabledSuccessfully = true;
            }
            catch (System.Runtime.InteropServices.COMException)
            {
                // This occurs with an HRESULT meaning 
                // "A different runtime was already bound to the legacy CLR version 2 activation policy."
                LegacyV2RuntimeEnabledSuccessfully = false;
            }
        }

        [System.Runtime.InteropServices.ComImport]
        [System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
        [System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")]
        private interface ICLRRuntimeInfo
        {
            void xGetVersionString();
            void xGetRuntimeDirectory();
            void xIsLoaded();
            void xIsLoadable();
            void xLoadErrorString();
            void xLoadLibrary();
            void xGetProcAddress();
            void xGetInterface();
            void xSetDefaultStartupFlags();
            void xGetDefaultStartupFlags();

            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType = System.Runtime.CompilerServices.MethodCodeType.Runtime)]
            void BindAsLegacyV2Runtime();
        }
    }
' VB.NET
Public NotInheritable Class RuntimePolicyHelper
    Public Shared Property LegacyV2RuntimeEnabledSuccessfully() As Boolean
        Get
            Return m_LegacyV2RuntimeEnabledSuccessfully
        End Get
        Private Set
            m_LegacyV2RuntimeEnabledSuccessfully = Value
        End Set
    End Property
    Private Shared m_LegacyV2RuntimeEnabledSuccessfully As Boolean

    Shared Sub New()
        Dim clrRuntimeInfo As ICLRRuntimeInfo = DirectCast(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty, GetType(ICLRRuntimeInfo).GUID), ICLRRuntimeInfo)
        Try
            clrRuntimeInfo.BindAsLegacyV2Runtime()
            LegacyV2RuntimeEnabledSuccessfully = True
        Catch generatedExceptionName As System.Runtime.InteropServices.COMException
            ' This occurs with an HRESULT meaning 
            ' "A different runtime was already bound to the legacy CLR version 2 activation policy."
            LegacyV2RuntimeEnabledSuccessfully = False
        End Try
    End Sub

    <System.Runtime.InteropServices.ComImport>
    <System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)>
    <System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")>
    Private Interface ICLRRuntimeInfo
        Sub xGetVersionString()
        Sub xGetRuntimeDirectory()
        Sub xIsLoaded()
        Sub xIsLoadable()
        Sub xLoadErrorString()
        Sub xLoadLibrary()
        Sub xGetProcAddress()
        Sub xGetInterface()
        Sub xSetDefaultStartupFlags()
        Sub xGetDefaultStartupFlags()

        <System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType:=System.Runtime.CompilerServices.MethodCodeType.Runtime)>
        Sub BindAsLegacyV2Runtime()
    End Interface
End Class

A utilização dessa classe é muito simples. Nós só temos que acessar a propriedade “LegacyV2RuntimeEnabledSuccessfully“, que retornará “true” caso o elemento tenha sido adicionado no startup com sucesso e “false” caso contrário. No nosso exemplo, como a exibição dos relatórios está sendo feita diretamente no projeto da aplicação, o lugar ideal para adicionarmos essa chamada seria no método “Main“:

        // C#
        [STAThread]
        static void Main()
        {
            if (RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new FormRelatorio());
            }
            else
            {
                MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

No VB.NET, por padrão, você não encontrará um método “Main” em projetos do tipo “Windows Forms Application“. Nesse caso, ou você altera as propriedades do projeto de forma que ele tenha um método “Main” (como descrito nesta thread do StackOverflow) ou você coloca a chamada dessa propriedade no construtor do formulário onde o relatório está sendo exibido:

    ' VB.NET
    Public Sub New()
        If (Not RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully) Then
            MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

Pronto! Com essas alterações, você não precisa mais definir o elemento no arquivo app.config. Ele será automaticamente adicionado em tempo de execução.

Concluindo

O disparo de uma “FileNotFoundException” é algo comum ao utilizarmos o controle do Crystal Reports em aplicações desenvolvidas com o .NET Framework 4 ou superior. Esse erro pode ser facilmente corrigido de duas maneiras: fazendo um ajuste no arquivo app.config ou adicionando dinamicamente um elemento nas políticas de runtime em tempo de execução. Neste artigo você conferiu as duas maneiras de resolver esse problema.

E você, já passou por esse problema com o Crystal Reports? Conhecia a segunda metodologia de resolução? Qual das duas opções você achou melhor? Conte-nos mais detalhes 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll appeared first on André Alves de Lima.

Instalando o Reporting Services 2016 Express

$
0
0

Quem diria que aquela ferramenta singela de geração de relatórios lançada em 2004 iria alcançar a sua sexta edição em 2016? O Reporting Services cresceu muito desde a sua primeira edição, que foi disponibilizada como um add-on do SQL Server 2000! Eu só fico impressionado que, mesmo no Reporting Services 2016, depois de tanta evolução, ele continue não suportando a justificação de texto, que é uma funcionalidade tão básica. Mas, vamos deixar essa discussão para uma próxima oportunidade.

O que eu quero mostrar para você hoje é o processo de download, instalação, configuração padrão e publicação do primeiro relatório com o Reporting Services 2016 Express. Como você vai perceber logo no início, o processo ficou um pouco mais tranquilo nessa nova versão.

Baixando o Reporting Services 2016 Express

Antes de baixar qualquer versão do SQL Server 2016 Express, atente-se para o fato que o Reporting Services faz parte do que a Microsoft chama de “Advanced Services” do SQL Server. Até a versão 2014 do SQL Server, se quiséssemos que o Reporting Services fosse instalado em conjunto como SQL Server, nós tínhamos que baixar explicitamente o pacote de instalação do SQL Server with advanced services. Porém, a Microsoft alterou um pouco essa sistemática no SQL Server 2016, juntando tudo em um pacote único de instalação.

Se você procurar no Google por “SQL Server 2016 Express“, você provavelmente encontrará a página onde você pode baixar o instalador. Porém, quero deixar uma dica muito interessante aqui para você. Uns tempos atrás o Scott Hanselman (que trabalha no time de plataforma web na Microsoft), cansado da dificuldade de baixar as versões Express do SQL Server, preparou uma página com o link de download do SQL Server Express, desde a edição 2008 até a edição 2016. Essa página está disponível no seguinte endereço:

http://downloadsqlserverexpress.com

Não sei você, mas, eu sempre gosto de instalar todas as minhas ferramentas de desenvolvimento em inglês. Na minha opinião, essa é a maneira mais fácil de conseguir resolver problemas quando eles acontecem. Por exemplo, é muito mais fácil encontrar a solução para um problema se você procurar pela mensagem de erro em inglês do que se você procurar pela mensagem em português. Mesmo se você não domina muito o inglês, eu recomendo que você se esforce para trabalhar com as versões em inglês das ferramentas de desenvolvimento.

Entretanto, se você realmente não tiver condições de entender a interface da ferramenta em inglês e quiser instalar a versão em português, não se preocupe. O SQL Server 2016 está disponível em português. Você só precisa se atentar para escolher o idioma correto na hora de baixar o pacote de instalação:

Pré-requisitos de software e hardware

Os pré-requisitos de software e hardware estão claramente definidos na página de download do instalador:

Note que os pré-requisitos oficiais para o SQL Server 2016 não são lá grande coisa. Hoje em dia praticamente qualquer laptop ou desktop que você tenha atenderá esses pré-requisitos. Ah, e não se esqueça das limitações do SQL Server Express! Não importa a quantidade de memória que você tiver no seu computador, ele só consumirá no máximo 1 GB de memória RAM e cada banco (arquivo mdf) estará limitado ao tamanho de 10 GB de armazenamento (inclusive o banco de dados do Reporting Services).

Instalação passo a passo

Uma vez baixado o “pacote de instalação“, você notará que, na realidade, ele não é um pacote de instalação em si. Ele é somente uma “casca” onde você escolhe o que você quer baixar e instalar do SQL Server. Na primeira página do instalador, escolha a opção “Custom“. Com essa opção nós teremos a oportunidade de indicar que queremos instalar o Reporting Services também:

Na próxima tela, você só precisa escolher um local onde os arquivos temporários de instalação serão armazenados. Escolha a pasta desejada ou deixe a opção padrão e clique em “Install“.

Após o download do instalador, o assistente de instalação do SQL Server perguntará o que você quer instalar. No nosso caso, nós queremos instalar uma nova instância do SQL Server, então, temos que escolher a primeira opção:

A instalação do SQL Server 2016 com o Reporting Services é muito simples. Eu não vou mostrar aqui neste artigo as capturas de cada tela da instalação porque grande parte dela é o típico “next, next, finish” das instalações de aplicativos Windows. Vou mostrar para você somente as telas onde você tem que prestar atenção para escolher as opções corretas do Reporting Services.

A primeira tela que você tem que prestar muita atenção é a etapa “Feature Selection“. Essa tela é importantíssima. Aqui você precisa ter 100% de certeza de marcar a opção “Reporting Services – Native“, senão, obviamente, o Reporting Services não será instalado!

Uma outra opção que você deve tomar cuidado é, na etapa “Reporting Services Configuration“, não esqueça de marcar a opção “Install and configure“. Ao fazer isso, o próprio instalador configurará o Reporting Services com as opções padrão. Caso você escolha a outra opção (Install only), você terá que configurar posteriormente o Reporting Services através do Reporting Services Configuration Manager. Eu só recomendo essa opção se você realmente souber o que você está fazendo:

URLs do Reporting Services

Dependendo do computador onde você está instalando o SQL Server, a instalação pode demorar um pouco para completar. Eu fiz um teste de instalação em uma máquina virtual do Azure e o processo de instalação levou uns 15 minutos para ser finalizado. Uma vez tendo instalado com as opções que eu demonstrei anteriormente, vamos abrir o Reporting Services Configuration Manager para verificarmos as URLs que estão sendo utilizadas pelo Reporting Services. Você conseguirá encontrar o ícone para o aplicativo de configuração do Reporting Services na pasta “SQL Server 2016” do menu iniciar:

A primeira coisa que temos que fazer ao abrirmos o Reporting Services Configuration Manager é conectarmos à nossa instância do Reporting Services. Normalmente o próprio aplicativo encontrará a instância instalada no computador local e fará uma sugestão para que você se conecte àquela instância. Se você tiver instalado o Reporting Services em um outro computador, você terá que alterar as informações de conexão nesta tela:

Uma vez conectado ao Reporting Services, conseguiremos alterar todas as configurações possíveis através dessa ferramenta. Não vou entrar em detalhes nesse artigo sobre cada uma das possíveis configurações (talvez mais para frente eu escreva um outro artigo detalhando todas as opções). O que eu quero mostrar para você são as URLs que o SQL Server está utilizando para o Reporting Services. A primeira URL é o endereço do serviço em si. Você encontra essa URL na aba “Web Service URL“:

Como você pode notar, por padrão, o SQL Server utiliza a porta 80 seguido de “ReportServer_” e o nome da instância do SQL Server (no nosso caso, “SQLEXPRESS“). Se você tentar abrir essa URL no browser, você se deparará com algo parecido com isto:

Apesar do conteúdo não ser nada útil, esse endereço é muito importante. Esse é o endereço que precisaremos utilizar quando quisermos publicar um relatório ou quando quisermos exibir um relatório no controle do Report Viewer diretamente de dentro de uma aplicação (desktop ou web).

A próxima URL que temos à nossa disposição é a URL do portal do Reporting Services. Nesse portal conseguimos visualizar os relatórios e data sources que estão publicados no Reporting Services. Você encontra esse endereço na aba “Web Portal URL” da ferramenta de configuração:

Mais uma vez, por padrão, o SQL Server utiliza a porta 80. Porém, dessa vez a URL é seguida de “Reports_” e o nome da instância do SQL Server. Ao abrirmos esse endereço no browser, teremos o novo portal do Reporting Services 2016:

Por enquanto o portal está vazio, mas, assim que formos adicionando relatórios e data sources no nosso servidor, nós conseguiremos acessá-los visualmente através dessa URL.

Criando e publicando relatórios para o Reporting Services 2016

Para criarmos relatórios do Reporting Services 2016, nós podemos utilizar as ferramentas compatíveis com as versões anteriores do Reporting Services (SQL Server 2012 Report Builder ou ferramentas de design dentro do Visual Studio 2015 com o pacote de Data Tools) ou nós podemos utilizar a versão mais nova do Report Builder (2016), que já é compatível com o Reporting Services 2016.

Se você optar pelo Report Builder 2016, através do portal do Reporting Services você encontra um link para baixa-lo:

Não vou detalhar a instalação do Report Builder porque ela é uma instalação completamente tranquila. É só seguir o típico “next, next, finish” das instalações de aplicativos Windows que tudo vai dar certo.

Na tela inicial do Report Builder, escolha a opção de “Table or Matrix Wizard” e crie um relatório qualquer apontando para uma fonte de dados que esteja à sua disposição. No meu caso, eu utilizei o famoso banco de dados de exemplo do SQL Server (Adventure Works):

Uma vez criado o relatório, temos que salvá-lo no Reporting Services. Nessa etapa temos que prestar muita atenção para utilizarmos a URL correta do servidor (que deve ser a “Web Service URL“):

Com a conexão estabelecida, podemos agora salvar o relatório (através do menu File -> Save):

Feito isso, se atualizarmos o portal do Reporting Services, veremos que agora temos um novo relatório paginado disponível para execução:

Concluindo

O Reporting Services 2016 é sexta geração da ferramenta de relatórios “server side” da Microsoft, que vem sendo disponibilizada em conjunto com o SQL Server desde a versão 2000. Neste artigo você conferiu como baixar e instalar o Reporting Services 2016 Express utilizando as configurações padrão, bem como quais são as URLs que temos que nos atentar ao publicarmos os nossos relatórios no Reporting Services. Essa nova versão do Reporting Services está carregada de novas funcionalidades, que eu pretendo demonstrar em outros artigos desta série.

Antes de me despedir, 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Instalando o Reporting Services 2016 Express appeared first on André Alves de Lima.

Definindo a estrutura de campos do Crystal Reports sem DataSet ou classe

$
0
0

André, eu consigo criar relatórios do Crystal Reports sem DataSet nem classe?“. Resposta: consegue sim! Uns tempos atrás eu mostrei como imprimir dados de um DataGridView no Crystal Reports. Naquele artigo eu mostrei duas maneiras para definirmos a estrutura de dados do relatório: através de um DataSet e através de uma coleção de instâncias de uma classe. Mas, e se tivermos que desenhar o relatório antes mesmo que o DataSet ou classe tenham sido criados? Nesse caso, temos que recorrer aos arquivos TTX, que nada mais são que arquivos de texto contendo as definições dos campos de cada tabela do relatório. É justamente isso que eu vou mostrar para você no artigo de hoje. Vem comigo!

Criando um relatório com arquivo TTX

Primeiramente, vamos criar um novo relatório no nosso projeto. Só para simplificar as coisas, neste artigo eu vou utilizar um projeto do tipo “Windows Forms Application“. Você poderia utilizar exatamente a mesma sistemática com outros tipos de projetos, como WPF ou Web Forms.

Dentro desse projeto, adicione um novo relatório em branco, dando o nome de “RelatorioFuncionario“. Com o relatório criado, a primeira coisa que temos que fazer é definirmos a estrutura de dados do relatório. Normalmente, nessa etapa nós selecionaríamos um DataSet ou classe do nosso projeto que serviria como fonte de dados para o nosso relatório. Porém, como o objetivo desse artigo é justamente definir o relatório sem DataSet ou classe, nós teremos que criar a definição dos campos com um arquivo TTX.

Os arquivos TTX nada mais são que arquivos de texto que contém a definição de campos de uma tabela. Esse arquivo pode ser criado manualmente com um editor de texto, mas, essa não é a melhor maneira de fazermos isso. O próprio Crystal Reports tem um “gerador” de arquivos TTX. Para evitarmos problemas, vamos utilizar esse gerador do Crystal Reports.

Para acessarmos o gerador de arquivos TTX, nós temos que abrir a tela “Database Expert“:

Dentro da janela “Database Expert“, encontre o item “Field Definitions Only“, que está localizado dentro da categoria “Create New Connection” / “More Data Sources“. Clique no botão de expansão desse item:

Na janela “Field Definitions Only“, como ainda não temos nenhum arquivo TTX, vamos clicar na opção “Create File“:

Ao fazermos isso, a janela de “Database Definition Tool” será aberta. Nessa janela podemos adicionar os campos da nossa tabela, indicando os seus nomes, tipos de dados e até mesmo um dado de exemplo (opcional).

Neste artigo nós faremos um relatório bem simples de listagem de funcionários, portanto, vamos criar somente três campos. O campo FuncionarioID será do tipo numérico e os campos Nome e Sobrenome serão do tipo string. Note que para os campos do tipo string nós temos que definir a quantidade de caracteres. A quantidade máxima de caracteres dos campos string é 65534. Para qualquer campo que tenha um tamanho maior que esse, você terá que utilizar o tipo “Memo” ao invés de “String“:

Uma vez adicionados os campos, feche a janela. Ao fechar a janela o Crystal Reports perguntará o local onde você quer salvar esse novo arquivo TTX. Escolha o diretório desejado e clique em salvar. Atente-se para o fato que o nome do arquivo sempre corresponderá ao nome da tabela no relatório. Portanto, salve o arquivo com o nome de “Funcionario.ttx“, dessa forma o Crystal Reports utilizará o nome “Funcionario” para a tabela.

Com o arquivo criado e salvo, adicione a tabela “Funcionario” para a lista de tabelas do relatório:

Em seguida vamos arrastar os campos da tabela “Funcionario” para dentro do relatório e ele estará pronto para ser exibido na nossa aplicação:

E qual é a estrutura de um arquivo TTX?

Antes de prosseguirmos com a exibição do relatório, vamos dar uma olhada na estrutura do arquivo TTX. Se você abrir o arquivo que salvamos anteriormente em um editor de texto, você verá que o conteúdo dele será este:

FuncionarioID	Number		
Nome	String	65534	
Sobrenome	String	65534	

Note que a estrutura é muito simples. Em cada linha do arquivo temos a definição de uma coluna. A definição é composta pelo nome da coluna, tipo de dados, tamanho do campo e um dado de exemplo, tudo separado por “tabs“. Se por algum motivo você não quiser utilizar o editor de TTX do Crystal Reports, você pode criar ou editar esse arquivo “na mão” em um editor de texto sem problema algum.

Carregando o relatório com entidade anônima via LINQ

Agora que já temos o nosso relatório criado, vamos exibi-lo. Uns tempos atrás eu escrevi um artigo demonstrando como imprimir o conteúdo de um DataGridView no Crystal Reports. Nesse artigo eu mostrei dois tipos de carregamentos de dados nos relatórios do Crystal Reports: via DataSet ou via coleção de uma determinada classe. Só que eu esqueci que nós podemos enviar uma coleção de tipos anônimos gerados através de uma consulta LINQ! Então, eu vou mostrar para você neste artigo essa terceira maneira de enviar dados para o Crystal Reports: sem DataSet nem classe concreta.

Primeiramente, vamos adicionar o controle do Crystal Reports no nosso formulário e vamos escolher o relatório que criamos anteriormente:

Em seguida, vamos para o code-behind do formulário e, no construtor (ou no evento “Load” no caso do VB.NET), vamos criar um array com algumas informações de funcionários e, logo em seguida, vamos construir uma consulta LINQ que transformará esse array em uma coleção de um tipo anônimo com as colunas que precisamos no nosso relatório (FuncionarioID, Nome e Sobrenome). Por fim, a única coisa que fica faltando é passar essa coleção para o relatório e chamar um “Refresh” para os dados serem carregados:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();

            var funcionarios = new object[]
            {
                new object[] { 1, "André", "Lima" },
                new object[] { 2, "Fulano", "de Tal" },
                new object[] { 3, "Beltrano", "da Silva" },
            };
            var colecao = from object[] funcionario in funcionarios
                          select new
                          {
                              FuncionarioID = funcionario[0],
                              Nome = funcionario[1],
                              Sobrenome = funcionario[2]
                          };
            RelatorioFuncionario1.SetDataSource(colecao);
            RelatorioFuncionario1.Refresh();
        }
    ' VB.NET
    Private Sub FormRelatorio_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Funcionarios As Object() =
            {
                New Object() {1, "André", "Lima"},
                New Object() {2, "Fulano", "de Tal"},
                New Object() {3, "Beltrano", "da Silva"}
            }
        Dim Colecao = From Funcionario In Funcionarios
                      Select New With
                      {
                        .FuncionarioID = Funcionario(0),
                        .Nome = Funcionario(1),
                        .Sobrenome = Funcionario(2)
                      }
        RelatorioFuncionario1.SetDataSource(Colecao)
        RelatorioFuncionario1.Refresh()
    End Sub

E com isso, ao executarmos a nossa aplicação, veremos que os dados serão exibidos no relatório com sucesso:

Concluindo

Em algumas situações, pode ser que tenhamos que desenhar um relatório antes que o DataSet ou classe de dados tenham sido gerados. Nesse caso, se não quisermos criar um DataSet ou classe “fake“, nós temos a opção de criarmos arquivos TTX. Cada arquivo desse tipo contém a definição de campos de uma tabela que será utilizada no relatório do Crystal Reports.

Nesse artigo você conferiu como gerar relatórios do Crystal Reports sem DataSet nem classe, através de um arquivo TTX. Além disso, eu mostrei também como enviar dados para o relatório sem utilizar DataSets ou classes! Esse tipo de carregamento não se aplica a muitos cenários, mas, fica como dica para caso você precise na sua aplicação.

Antes de me despedir, 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Definindo a estrutura de campos do Crystal Reports sem DataSet ou classe appeared first on André Alves de Lima.

Report Viewer foi descontinuado pela Microsoft? Não!

$
0
0

É sempre a mesma coisa. A cada nova versão do Visual Studio surge aquele frio na barriga com relação à ferramenta de relatórios da Microsoft: será que o Report Viewer foi descontinuado? Isso aconteceu com bastante força no lançamento do Visual Studio 2015, quando a Microsoft decidiu remover o Report Viewer da instalação “típica” do Visual Studio, fazendo com que ele só aparecesse se você selecionasse o item “SQL Server Data Tools” durante a instalação do Visual Studio (inclusive eu mostro como fazer isso neste artigo).

Com a nova versão do Visual Studio (até o momento denominada de Visual Studio “15“), isso não poderia ser diferente. Nessa versão, a sensação é ainda pior. Mesmo instalando o “SQL Server Data Tools“, o Report Viewer ainda não aparece! Por que será? Teria a Microsoft realmente descontinuado o Report Viewer?

Não se assuste, até agora o Report Viewer não foi descontinuado pela Microsoft. Porém, a partir da próxima versão do Visual Studio, o modelo de distribuição dos controles e designers passarão por grandes mudanças. Confira o restante do artigo para ver os detalhes dessa mudança.

O novo modelo de distribuição do Report Viewer

Por ser o produto que eu escolhi focar nos últimos dois anos, eu tenho acompanhado de perto o que está acontecendo no mundo do Report Viewer / Reporting Services 2016. Até configurei uns alertas no Google para alguns termos relacionados a essas tecnologias. Dessa forma, sempre que algum novo artigo é indexado pelo Google, eu fico sabendo e vou lá dar uma olhada.

Foi através desse tipo de alerta que eu fiquei sabendo desta thread nós fóruns da MSDN americana. Nessa thread descobri qual será o novo modelo de distribuição do controle e do designer do Report Viewer / Reporting Services integrado ao Visual Studio.

Vejam só este trecho da thread, publicado pelo Brad Syrupta, engenheiro de software que trabalha no time de SQL Server BI na Microsoft:

Ou seja, o controle do Report Viewer não será mais distribuído em conjunto com o Visual Studio. A partir da próxima versão, ele será distribuído através de um pacote do NuGet. Essa é uma estratégia que a Microsoft tem utilizado com diversas outras tecnologias (como Entity Framework, por exemplo). Já a parte de designer dos relatórios será disponibilizada através de uma extensão do Visual Studio (VSIX), que será publicada na Visual Studio Gallery.

Qual o motivo da mudança?

Não podemos nos esquecer que a Microsoft é uma empresa gigantesca, formada por inúmeros times. Agora, vamos pensar somente na divisão de ferramentas para desenvolvimento de software (Visual Studio, .NET, etc). Já pensou a dificuldade que deve ser para sincronizar os diversos times envolvidos nesse processo?

Desde o Visual Studio 2005 até o Visual Studio 2015, o controle e o designer de relatórios do Report Viewer foram distribuídos juntamente com o Visual Studio. Isso traz um grande desafio para os times, uma vez que nem sempre as releases do Visual Studio estão sincronizadas com as releases do SQL Server (time por trás da experiência de relatórios RDL e RDLC).

Para tentar diminuir esse stress (entre outras coisas, como custos com recursos, manutenção, estratégias do produto, etc), a nova metodologia de distribuição fará com que as releases do controle do Report Viewer não estejam mais atreladas às releases do Visual Studio. Ou seja, nós não vamos mais precisar esperar que uma nova versão do Visual Studio seja lançada para podermos utilizar as novas versões do controle e do designer do Report Viewer.

Veja só este outro trecho do post do Brad Syrupta:

Testando a versão preview do controle do Report Viewer 2016

A primeira versão de testes do controle do Report Viewer 2016 foi lançada no dia 20 de setembro. Tanto o controle Windows Forms quanto o controle Web Forms estão disponíveis através de um pacote do NuGet. Para instalar o pacote, abra a janela de gerenciamento de pacotes do NuGet e procure por “reportviewercontrol“:

Outra opção é fazer a instalação do pacote através do Package Manager Console, utilizando estes nomes de pacotes (WinForms ou WebForms, dependendo do tipo do seu projeto):

– Microsoft.ReportingServices.ReportViewerControl.WinForms.Preview

– Microsoft.ReportingServices.ReportViewerControl.WebForms.Preview

Para mais informações sobre o gerenciamento de pacotes do NuGet através do Visual Studio, confira este artigo.

Para projetos Windows Forms, você não precisa fazer nenhum ajuste e o novo controle deve funcionar normalmente. Obviamente, se você já utilizava uma versão anterior do controle do Report Viewer, você precisa remover as referências antigas antes de adicionar o pacote da versão preview.

Para projetos web, se você já utilizava uma versão anterior do controle do Report Viewer, você terá que alterar algumas referências, apontando para a versão mais nova do controle (13.0.0.0). O primeiro lugar que você tem que alterar é a tag “Register” na página onde você utiliza o controle do Report Viewer:

O novo conteúdo dessa tag deve ser o seguinte:

<%@ Register assembly="Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" namespace="Microsoft.Reporting.WebForms" tagprefix="rsweb" %>

Depois, temos que ajustar alguns pontos do arquivo web.config. O primeiro lugar é a referência para os assemblies (tag “assemblies“, dentro de “compilation“). Você deve assegurar-se que essas referências estejam apontando para a versão mais nova do controle:

      <assemblies>
        <add assembly="Microsoft.ReportViewer.Common, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" />
        <add assembly="Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" />
      </assemblies>

Em seguida, temos que alterar o conteúdo da tag “httpHandlers“:

    <httpHandlers>
      <add path="Reserved.ReportViewerWebControl.axd" verb="*" type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91" validate="false" />
    </httpHandlers>

Por fim, temos que fazer a mesma alteração na tag “handlers“, dentro de “system.webserver“:

    <handlers>
      <add name="ReportViewerWebControlHandler" verb="*" path="Reserved.ReportViewerWebControl.axd" preCondition="integratedMode"
        type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845DCD8080CC91"/>
    </handlers>

Com essas alterações, o seu projeto deve rodar sem nenhum problema de compilação.

Resultados Windows Forms

Depois de testar o controle do Windows Forms, eu não consegui detectar nenhuma diferença visual no controle. Também fiz um teste de performance com um relatório contendo um milhão de registros e não consegui notar nenhuma diferença na velocidade no processamento. Veja só a “cara” do controle, que é idêntica à da versão anterior:

Resultados Web Forms

Por outro lado, o controle Web Forms foi completamente reformulado. Pelo que andei lendo, a Microsoft decidiu abandonar de vez a utilização de ActiveX para a impressão dos relatórios e está renderizando todo o conteúdo em HTML5. Porém, no relatório que eu testei as coisas não funcionaram muito bem:

Veja só o mesmo relatório sendo exibido sem problema algum na versão anterior do Report Viewer:

Testei o mesmo relatório tanto no Google Chrome quanto no Internet Explorer e Edge. O resultado foi o mesmo. Tentei também adicionar a tag “meta” forçando que sites intranet não sejam renderizados em modo compatibilidade (como indicado na seção “Common issuesdeste link), mas, também não tive sucesso.

Consertando possíveis problemas com o controle web

Depois de finalizar a escrita deste artigo, eu estava dando uma olhada no fórum de Reporting Services da MSDN americana e acabei encontrando esta thread. Seguindo as instruções apresentadas nela, a versão nova do controle web do Report Viewer funcionou corretamente:

O problema acontece basicamente porque a última versão do SQL Server Data Tools (instalada com o Visual Studio 2015 e seus updates) adiciona no GAC uma versão do novo controle que ainda não estava finalizada. E como as dlls do GAC têm mais prioridade do que as dlls presentes na pasta da aplicação, o IIS acaba carregando essa versão incorreta do GAC.

Para corrigirmos esse problema, precisamos remover as dlls problemáticas do GAC. Conseguimos remover dlls do GAC utilizando a ferramenta “gacutil“. Essa ferramenta fica armazenada na pasta “C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin“. Abra um prompt de comando elevado (como administrador), navegue até esse diretório e tente executar os seguintes comandos para remover todas as dlls problemáticas do Report Viewer do GAC:

gacutil /u "Microsoft.ReportViewer.WebForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.WinForms, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.WebDesign, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f
gacutil /u "Microsoft.ReportViewer.Common, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" /f

Se esses comandos forem executados sem nenhum erro, o seu ambiente já estará pronto. Porém, no meu caso eu recebi um erro ao tentar remover as dlls do GAC:

Unable to uninstall: assembly is required by one or more applications

Pending references:

SCHEME: <WINDOWS_INSTALLER> ID: <MSI> DESCRIPTION:<Windows Installer>

Depois de pesquisar um pouco sobre esse erro, acabei encontrando este artigo, que explica claramente que o problema é que essas dlls estão sendo referenciadas pelo Windows Installer no registro do Windows. Se isso também estiver acontecendo no seu ambiente, você terá que primeiramente remover as suas referências do Windows Installer no registro do Windows.

No meu computador, eu encontrei as referências na pasta “HKLM\SOFTWARE\Classes\Installer\Assemblies\Global“. Porém, pode ser que em algumas instalações as referências estejam em “HKCU\Software\Microsoft\Installer\Assemblies\Global“, portanto, é importante procurar nessas duas pastas do registro. Uma vez encontradas as chaves do Report Viewer, delete tudo o que tiver versão “13” (não esqueça de fazer um backup do registro antes de deletar as chaves!):

Em seguida, execute novamente os comandos para remover as dlls do GAC e veja que, dessa vez, os comandos funcionam com sucesso:

Concluindo

Sempre que uma nova versão do Visual Studio é lançada (ou está prestes a ser lançada), surgem os rumores que o Report Viewer esteja sendo descontinuado pela Microsoft. Até hoje, isso nunca se concretizou e espero que não se concretize num futuro próximo.

Como você conferiu neste artigo, por razões estratégicas, a Microsoft está alterando o modelo de distribuição do Report Viewer a partir da próxima versão do Visual Studio. Esse novo modelo não será mais dependente das releases do Visual Studio, ou seja, poderemos ter novas versões do controle e das ferramentas de design independentemente das datas de release do Visual Studio.

O que você achou dessa nova estratégia? E o que você achou do novo visual do controle web do Report Viewer, desenvolvido completamente em HTML5? Fico aguardando as suas opiniões 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

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Report Viewer foi descontinuado pela Microsoft? Não! appeared first on André Alves de Lima.

Como imprimir um formulário no Windows Forms com C# e VB.NET?

$
0
0

Esses dias atrás eu recebi um comentário no meu artigo sobre impressão direta com C# perguntando se seria possível imprimirmos um formulário no Windows Forms. Eu sabia que isso era possível, então, dei uma pesquisada rápida e encaminhei este link para ele. Porém, como eu já tinha recebido esse tipo de pergunta mais de uma vez (além das diversas vezes que já me deparei com essa dúvida nos fóruns da MSDN), percebi que essa era uma demanda recorrente. Então, eu pensei: que tal escrever um artigo sobre essa funcionalidade? Quer conferir o resultado? Então, vamos lá!

Capturando uma imagem do formulário

Imprimir o formulário no Windows Forms não é uma tarefa nada difícil, principalmente se pudermos considerar a borda na do formulário. Um simples objeto do tipo Graphics em conjunto com a chamada do seu método CopyFromScreen atende facilmente essa demanda. Esse método faz a captura de uma determinada área da tela. Dessa forma, se nós simplesmente passarmos as coordenadas do formulário, teremos o resultado esperado. Vamos ver como podemos fazer isso?

Primeiramente, vamos criar um novo projeto do tipo “Windows Forms Application“. Dentro do formulário, vamos adicionar um botão (“imprimirButton“) e um componente do tipo PrintDocument (“printDocument“):

Em seguida, no evento “Click” do botão, vamos chamar o método “Print” do nosso PrintDocument:

        // C#
        private void imprimirButton_Click(object sender, EventArgs e)
        {
            printDocument.Print();
        }
    ' VB.NET
    Private Sub ImprimirButton_Click(sender As Object, e As EventArgs) Handles ImprimirButton.Click
        PrintDocument.Print()
    End Sub

A definição do que será impresso pelo componente PrintDocument é feita através do seu evento “PrintPage“. Para implementarmos o código nesse evento, vamos até o designer do formulário, clicamos no componente PrintDocument e, na janela de propriedades, nós temos que encontrar o evento “PrintPage” e, em seguida, clicamos duas vezes para criarmos um novo manipulador para esse evento:

Dentro desse evento, vamos capturar o desktop, limitando para a área onde o formulário está localizado:

        // C#
        private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            var image = new Bitmap(this.Width, this.Height);
            var graphics = Graphics.FromImage(image);
            graphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size);
            e.Graphics.DrawImage(image, 20, 20);
        }
    ' VB.NET
    Private Sub PrintDocument_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument.PrintPage
        Dim Image = New Bitmap(Me.Width, Me.Height)
        Dim graphics = System.Drawing.Graphics.FromImage(Image)
        graphics.CopyFromScreen(Me.Location.X, Me.Location.Y, 0, 0, Me.Size)
        e.Graphics.DrawImage(Image, 20, 20)
    End Sub

Nota: a imagem do formulário será impressa na coordenada 20,20 da página. Se você quiser imprimir o formulário em uma outra coordenada, basta alterar os dois últimos valores dos parâmetros do método “DrawImage”.

Agora execute a aplicação e clique no botão “Imprimir“. A impressão será feita na impressora “padrão“. Para alterar a impressora, você pode configurar o nome da impressora na propriedade “PrinterSettings.PrinterName” (do componente PrintDocument) ou implementar uma funcionalidade onde o usuário possa escolher qual impressora utilizar, como eu mostrei no meu artigo sobre impressão direta na impressora. O resultado da impressão será parecido com a imagem abaixo:

Notou o problema? O método “CopyFromScreen” realmente copia tudo o que está sendo exibido na tela no momento da sua chamada, inclusive diálogos que possam estar por cima do formulário (como foi o caso do diálogo de impressão que foi capturado no nosso exemplo). Para que o diálogo não seja capturado na impressão, temos que chamar o método “CopyFromScreen” antes de chamarmos o método “Print” do “PrintDialog“, armazenando o resultado em uma variável no nível do formulário, que posteriormente será utilizada dentro do evento “PrintPage“.

Dito isso, a primeira coisa que temos que fazer é extrairmos a captura em um método (que daremos o nome de “CapturarForm“):

        // C#
        Bitmap captura = null;

        private void CapturarForm()
        {
            captura = new Bitmap(this.Width, this.Height);
            var graphics = Graphics.FromImage(captura);
            graphics.CopyFromScreen(this.Location.X, this.Location.Y, 0, 0, this.Size);
        }
    ' VB.NET
    Private Captura As Bitmap

    Private Sub CapturarForm()
        Captura = New Bitmap(Me.Width, Me.Height)
        Dim graphics = System.Drawing.Graphics.FromImage(Captura)
        graphics.CopyFromScreen(Me.Location.X, Me.Location.Y, 0, 0, Me.Size)
    End Sub

Em seguida, vamos ajustar o código que faz a impressão, de forma que esse novo método seja chamado antes do “Print” do “PrintDocument” e que o atributo “Captura” seja utilizado dentro do evento “PrintPage“:

        // C#
        private void imprimirButton_Click(object sender, EventArgs e)
        {
            CapturarForm();
            printDocument.Print();
        }

        private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            e.Graphics.DrawImage(captura, 20, 20);
        }
    ' VB.NET
    Private Sub ImprimirButton_Click(sender As Object, e As EventArgs) Handles ImprimirButton.Click
        CapturarForm()
        PrintDocument.Print()
    End Sub

    Private Sub PrintDocument_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument.PrintPage
        e.Graphics.DrawImage(Captura, 20, 20)
    End Sub

Pronto! Veja só o resultado na imagem a seguir:

Como você pode perceber, o diálogo de impressão não é mais capturado na imagem. Porém, você viu que ainda temos um outro problema para resolver? No Windows 10, temos esse efeito de transparência ao redor da janela, que faz com que o conteúdo que estiver embaixo do formulário seja impresso também.

Desconsiderando o efeito de transparência do Windows 10

Depois de procurar bastante, acabei encontrando duas threads no StackOverflow mostrando como pegar as coordenadas do formulário desconsiderando o efeito de transparência da borda. A solução consiste em chamar um método da dll “dwmapi“, que retornará as coordenadas verdadeiras da janela.

Para consertarmos esse problema, vamos criar uma nova classe no nosso projeto, a qual daremos o nome de “WindowHelper“. O conteúdo dessa classe deverá ser o seguinte:

    // C#
    public static class WindowHelper
    {
        [Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
        public struct Rect
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;

            public System.Drawing.Rectangle ToRectangle()
            {
                return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom);
            }
        }

        [System.Runtime.InteropServices.DllImport(@"dwmapi.dll")]
        public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);

        public enum Dwmwindowattribute
        {
            DwmwaExtendedFrameBounds = 9
        }
    }
' VB.NET
Public Module WindowHelper
    <Serializable, System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)>
    Public Structure Rect
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer

        Public Function ToRectangle() As System.Drawing.Rectangle
            Return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom)
        End Function
    End Structure

    <System.Runtime.InteropServices.DllImport("dwmapi.dll")>
    Public Function DwmGetWindowAttribute(hwnd As IntPtr, dwAttribute As Integer, ByRef pvAttribute As Rect, cbAttribute As Integer) As Integer
    End Function

    Public Enum Dwmwindowattribute
        DwmwaExtendedFrameBounds = 9
    End Enum
End Module

Em seguida, temos que ajustar o nosso método que faz a captura do formulário, de forma que ele utilize os valores retornados por essa nova classe que acabamos de criar:

        // C#
        private void CapturarForm()
        {
            WindowHelper.Rect rect;
            WindowHelper.DwmGetWindowAttribute(this.Handle, (int)WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds,
                out rect, System.Runtime.InteropServices.Marshal.SizeOf(typeof(WindowHelper.Rect)));
            var rectangle = rect.ToRectangle();

            captura = new Bitmap(rectangle.Width, rectangle.Height);
            var graphics = Graphics.FromImage(captura);
            graphics.CopyFromScreen(rectangle.Left, rectangle.Top, 0, 0, rectangle.Size);
        }
    ' VB.NET
    Private Sub CapturarForm()
        Dim Rect As WindowHelper.Rect
        WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds),
                                           Rect, System.Runtime.InteropServices.Marshal.SizeOf(GetType(WindowHelper.Rect)))
        Dim Rectangle = Rect.ToRectangle()

        Captura = New Bitmap(Rectangle.Width, Rectangle.Height)
        Dim Graphics = System.Drawing.Graphics.FromImage(Captura)
        Graphics.CopyFromScreen(Rectangle.Left, Rectangle.Top, 0, 0, Rectangle.Size)
    End Sub

Execute a aplicação, clique no botão “Imprimir” e veja o novo resultado:

Agora sim, hein?

Imprimindo o formulário sem a borda

Em algumas situações, pode ser que precisemos imprimir somente o conteúdo interno do formulário, ou seja, desconsiderando totalmente as bordas. Pesquisei um pouco sobre esse tema, encontrei algumas possibilidades para calcularmos as coordenadas da área interna do formulário, mas, todas as opções me pareceram muito “gambiarra“.

Uma alternativa bem simples nesse caso seria, antes de realizarmos a captura, nós alterarmos o estilo da janela, removendo as bordas através da propriedade FormBorderStyle. Depois da captura, nós restauramos o estilo de borda anterior. Eu sei que essa solução não é das mais bonitas, mas, eu acho que às vezes a solução mais “feia” acaba sendo a mais recomendada. Imagina se nós utilizamos um método de calcular as coordenadas internas do formulário e na próxima versão do Windows esse método para de funcionar? Melhor evitar essa situação, não é mesmo?

Dito isso, para capturarmos o formulário sem as bordas, nós poderíamos fazer o seguinte ajuste no método “CapturarForm“:

        // C#
        private void CapturarForm()
        {
            var formBorderStyleAnterior = this.FormBorderStyle;

            try
            {
                this.FormBorderStyle = FormBorderStyle.None;

                WindowHelper.Rect rect;
                WindowHelper.DwmGetWindowAttribute(this.Handle, (int)WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds,
                    out rect, System.Runtime.InteropServices.Marshal.SizeOf(typeof(WindowHelper.Rect)));
                var rectangle = rect.ToRectangle();

                captura = new Bitmap(rectangle.Width, rectangle.Height);
                var graphics = Graphics.FromImage(captura);
                graphics.CopyFromScreen(rectangle.Left, rectangle.Top, 0, 0, rectangle.Size);
            }
            finally
            {
                this.FormBorderStyle = formBorderStyleAnterior;
            }
        }
    ' VB.NET
    Private Sub CapturarForm()
        Dim FormBorderStyleAnterior = Me.FormBorderStyle

        Try
            Me.FormBorderStyle = FormBorderStyle.None

            Dim Rect As WindowHelper.Rect
            WindowHelper.DwmGetWindowAttribute(Me.Handle, CInt(WindowHelper.Dwmwindowattribute.DwmwaExtendedFrameBounds),
                                           Rect, System.Runtime.InteropServices.Marshal.SizeOf(GetType(WindowHelper.Rect)))
            Dim Rectangle = Rect.ToRectangle()

            Captura = New Bitmap(Rectangle.Width, Rectangle.Height)
            Dim Graphics = System.Drawing.Graphics.FromImage(Captura)
            Graphics.CopyFromScreen(Rectangle.Left, Rectangle.Top, 0, 0, Rectangle.Size)
        Finally
            Me.FormBorderStyle = FormBorderStyleAnterior
        End Try
    End Sub

Nota: o bloco try-finally assegura que o FormBorderStyle anterior seja sempre restaurado, independentemente se uma exceção tiver sido disparada durante o processo de impressão.

Veja só o resultado da impressão após essas alterações:

Imprimindo uma área específica do formulário

Outra demanda que pode surgir é a necessidade de imprimirmos somente uma área específica do formulário, e não o formulário inteiro. Isso é muito fácil de ser resolvido. Basta ajustarmos os valores de tamanho e coordenadas utilizados no método “CapturarForm“.

Por exemplo, se quisermos imprimir uma área de 200 x 200 pixels do nosso formulário, começando na coordenada 15, 15, o código do método “CapturarForm” ficaria assim:

        // C#
        private void CapturarForm()
        {
            captura = new Bitmap(200, 200);
            var graphics = Graphics.FromImage(captura);
            graphics.CopyFromScreen(this.Location.X + 15, this.Location.Y + 15, 0, 0, new Size(200, 200));
        }
    ' VB.NET
    Private Sub CapturarForm()
        Captura = New Bitmap(200, 200)
        Dim Graphics = System.Drawing.Graphics.FromImage(Captura)
        Graphics.CopyFromScreen(Me.Location.X + 15, Me.Location.Y + 15, 0, 0, New Size(200, 200))
    End Sub

E este seria o resultado:

Concluindo

Apesar de não ser tão complicado imprimir um formulário no Windows Forms, existem alguns detalhezinhos que podem acabar nos deixando com o cabelo em pé. Principalmente o efeito de transparência nas bordas dos formulários do Windows 10, que pode se tornar um pesadelo nesse processo.

No artigo de hoje você conferiu como imprimir um formulário no Windows Forms e como lidar com o problema do efeito na borda do Windows 10. Além disso, vimos também como imprimir somente o conteúdo interno do formulário (desconsiderando as bordas) e como imprimir somente uma área específica do formulário.

E você? Já precisou imprimir um formulário no Windows Forms? Também passou por esses problemas que eu mencionei nesse artigo? Como é que você conseguiu resolver essa demanda? Fico aguardando os seus 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/menu-gui-interface-template-ui-303121/
https://pixabay.com/en/inkjet-print-printer-peripheral-147674/

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Como imprimir um formulário no Windows Forms com C# e VB.NET? appeared first on André Alves de Lima.

Viewing all 61 articles
Browse latest View live