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:
' 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.
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:
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:
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.
Não sei se você lembra, mas, tinha uma época em que as resoluções dos monitores eram meio que “fixas“. Se você tinha um computador “mediano“, provavelmente a resolução dele era 800×600. Caso você tivesse um pouco mais de grana, o luxo era investir em uma placa de vídeo e monitor que suportasse 1024×780.
Hoje em dia isso não é mais a realidade. Com as placas de vídeo e monitores evoluindo rapidamente, em pouco tempo surge uma nova resolução, cada vez maior. 720p, 1080p e agora 4k. E isso sem falar nos problemas que o ajuste de DPI podem trazer para o nosso aplicativo!
Normalmente, os aplicativos WPF se comportam muito bem quando alteramos a resolução e DPI do Windows, uma vez que, por padrão, ele trabalha com contêineres de layout e unidades independentes de resolução (e não pixels). Porém, no Windows Forms não é muito difícil termos problema com alterações de resolução. Pensando nisso, resolvi escrever este artigo detalhando como podemos ajustar o formulário dependendo da resolução no Windows Forms.
Criando o projeto de exemplo
Para entendermos os problemas que uma alteração na resolução ou DPI podem trazer para aplicativos Windows Forms, vamos construir um novo projeto do tipo “Windows Forms Application“. Nesse projeto, altere o formulário para que ele fique parecido com a imagem abaixo:
Esse é o típico exemplo em que teremos problema se o usuário estiver trabalhando em uma resolução maior do que a que desenvolvemos o nosso sistema. Nesse caso, o usuário provavelmente tentará aumentar o tamanho do formulário (ou até mesmo maximizar a janela) e, como no Windows Forms os componentes têm tamanho fixo por padrão, eles continuarão com o seu tamanho original. Veja o efeito neste gif animado:
Esse problema pode ser facilmente corrigido utilizando a propriedade “Anchor” dos controles.
Propriedade Anchor
A propriedade “Anchor“, como o próprio nome diz, implementa um efeito de “âncora” nos controles do Windows Forms. Por padrão, quando adicionamos um controle dentro de um formulário, ele vem com a âncora configurada para “Top, Left” (topo, esquerda). Isso faz com que o controle fique “grudado” no formulário, só que tomando como base o canto superior esquerdo.
Para melhorar o comportamento dos controles do exemplo apresentado anteriormente, podemos alterar a propriedade “Anchor” de alguns controles. Vamos alterar essa propriedade para “Top,Left,Right” nos TextBoxes e Button indicados em vermelho e “Top,Bottom,Left,Right” no DataGridView azul:
Com essas alterações, confira o novo comportamento dos controles:
Como você pode perceber, a propriedade “Anchor” cola a extremidade do controle a uma borda específica do formulário e faz com que ele se redimensione automaticamente quando o formulário muda de tamanho.
Para que você entenda melhor o efeito de cada uma das opções de “Anchor“, vou demonstrar todas as possibilidades com gifs animados abaixo:
Sem Anchor (None):
Top:
Bottom:
Top,Bottom:
Left:
Right:
Left,Right:
Bottom,Left,Right:
Top,Left,Right:
Top,Bottom,Left,Right:
Propriedade Dock
Além da propriedade “Anchor“, temos também à nossa disposição no Windows Forms a propriedade “Dock“. Com ela, podemos “docar” controles em uma extremidade do formulário (ou do contêiner onde o controle está posicionado). Por exemplo, ao configurarmos o “Dock” do DataGridView para “Bottom“, ele se comportaria da seguinte maneira:
Note que, ao configurarmos qualquer tipo de “Dock” o “Anchor” será automaticamente alterado para “Top,Left“. Não é possível utilizarmos o “Anchor” em combinação com o “Dock“. Levando isso em consideração, notamos que o “Dock” é mais utilizado somente quando queremos que um controle ocupe todo o espaço do formulário (ou contêiner pai). Para isso, configuramos a propriedade “Dock” para “Fill“:
TableLayoutPanel
Você já notou que no primeiro exemplo de “Anchor” apresentado neste artigo, o ComboBox e TextBox da primeira linha não ficam com o mesmo tamanho ao aumentarmos o tamanho do formulário? O ComboBox que não está com o “Anchor” fica com um tamanho estático enquanto que o TextBox com “Anchor” tem o seu tamanho aumentado automaticamente. Como poderíamos fazer para que o tamanho do ComboBox e TextBox aumentasse simultaneamente? Para isso, precisamos de um TableLayoutPanel.
Se você está acostumado com o WPF, o TableLayoutPanel nada mais é que o Grid. Com ele, podemos definir linhas e colunas que terão tamanhos fixos, automáticos (que têm o tamanho do controle que está dentro da célula) ou percentuais.
Dessa forma, para que o ComboBox e o TextBox cresçam proporcionalmente, podemos adicionar um TableLayoutPanel com duas linhas e duas colunas. Esse controle se encontra dentro da categoria “Containers” na caixa de ferramentas:
Uma vez adicionado o TableLayoutPanel, arraste os Labels, ComboBox e TextBox para dentro do TableLayoutPanel:
Feito isso, vá para a tela de configuração das linhas e colunas do TableLayoutPanel:
Dentro da tela de configuração, escolha a opção “Rows” e configure ambas as linhas para tamanho “AutoSize“. Com essa configuração, as linhas terão automaticamente o tamanho do controle que está localizado dentro das células:
Agora, você se lembra que acabamos de estudar os efeitos da propriedade “Dock“? Lembra que eu falei sobre a configuração “Fill” do “Dock“? Pois bem, nesse caso, se configurarmos a propriedade “Dock” do ComboBox e TextBox para “Fill“, eles serão automaticamente redimensionados de acordo com o tamanho das colunas do TableLayoutPanel. E se, por fim, configurarmos o “Anchor” do TableLayoutPanel para “Top,Left,Right” teremos o seguinte resultado:
Percebeu que agora o ComboBox e TextBox estão aumentando e diminuindo proporcionalmente, de forma que cada um ocupe 50% da largura do formulário? Bem bacana, não é mesmo?
Ajuste de DPI
Até esse ponto, nós só falamos do ajuste automático dos controles quando alteramos o tamanho do formulário. Porém, como fica a situação em que o usuário tenha aumentado o DPI? Para quem não sabe, DPI é a porcentagem que os controles ocupam de acordo com a resolução da tela. Essa opção pode ser alterada nas configurações de resolução de tela, clicando no botão “Make text and other items larger or smaller“:
Quando a configuração está em 100%, não temos nenhum problema. Porém, quando o usuário altera essa configuração para outro valor, dependendo do manifesto da aplicação, ela pode acabar ficando toda desfigurada.
Por padrão, os projetos Windows Forms não são “DPI aware“. Ou seja, elas não implementam ajuste automático dos controles dependendo do DPI (como o WPF faz automaticamente). Dessa forma, se não alterarmos nenhuma configuração relacionada ao DPI e executarmos a nossa aplicação em um computador que está com um DPI diferente, as janelas serão automaticamente redimensionadas dependendo da configuração.
Porém, um detalhe importante é que o Windows fará esse redimensionamento como se ele estivesse “esticando” um bitmap. Ou seja, conseguiremos reparar uma certa perda de qualidade visual no aplicativo (mais especificamente, a aplicação parecerá um pouco “borrada“).
Uma outra opção que temos para solucionar o problema da alteração do DPI é configurarmos o nosso projeto Windows Forms como “DPI aware“. Podemos fazer isso adicionando um arquivo de manifesto no projeto incluindo a seguinte tag:
Com isso, o nosso aplicativo não será mais redimensionado automaticamente, ou seja, ele ficará do mesmo tamanho em que ele foi desenvolvido. Isso pode causar o efeito do aplicativo ficar extremamente pequeno caso a resolução e o DPI estejam muito altos.
Por fim, é possível ainda lidarmos com a alteração de DPI manualmente, redimensionando os controles da maneira que quisermos. Isso pode ser feito através de um override no método “ScaleControl” dos formulários da aplicação. Eu não recomendo essa alternativa, uma vez que ela é muito complicada de ser implementada e difícil de ser mantida. Porém, caso você se interesse sobre esse tema, aqui vão algumas referências:
Apesar do Windows Forms não implementar o ajuste automático dos controles dependendo da resolução e DPI (como no WPF), é possível fazermos esses tipos de ajustes para deixarmos os nossos formulários mais amigáveis com diferentes resoluções. Não é uma tarefa fácil, mas, também não é uma tarefa impossível.
Neste artigo você conferiu como utilizar as propriedades “Anchor” e “Dock” para ajustarmos os tamanhos dos controles automaticamente dependendo do tamanho do formulário. Além disso, vimos também como aumentar o tamanho de controles proporcionalmente através do TableLayoutPanel. Por fim, vimos uma breve explicação de como o Windows Forms lida com a alteração de DPI.
E você, já teve esse tipo de problema com as suas aplicações Windows Forms? Como é que você acabou resolvendo? Conte para a gente nos comentários se você utilizou “Anchor“, “Dock” ou outro tipo de solução.
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.
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?
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:
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:
' 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:
' 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:
' 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:
' 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.
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.
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:
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:
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.
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.
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.
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:
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:
' 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:
' 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.
Não é segredo para ninguém que os arquivos PDF dominaram o mercado. Praticamente todas as ferramentas que lidam com documentos possuem algum tipo de exportação para PDF. Além disso, existem inúmeras impressoras PDF disponíveis para download, muitas delas gratuitas. Há uns tempos atrás eu até mostrei aqui no site como gerar arquivos PDF com o C#. Mas, como é que fica se nós quisermos exibir um PDF no Windows Forms?
Nesse caso nós temos algumas opções, que vão desde a metodologia mais simples e barata (abrir o Adobe Reader para exibir o documento) até a mais cara (utilizar controles pagos). O objetivo desse artigo é mostrar cada uma dessas alternativas para que você possa escolher qual opção utilizar na sua aplicação.
Criando o projeto de exemplo
Antes de avaliarmos as nossas opções, vamos criar um pequeno projeto de exemplo, utilizando o template “Windows Forms Application“. Nesse projeto iremos conferir a implementação das três primeiras opções apresentadas abaixo.
Uma vez criado o projeto, ajuste o formulário de forma que ele fique parecido com a imagem abaixo:
Como você pode perceber, temos três botões no formulário (processStartButton, webBrowserButton e componenteCOMButton), cada um representando uma das opções que serão demonstradas logo a seguir.
Nota: para testarmos cada uma das opções, utilizaremos um arquivo PDF de exemplo, que é o “Visual Studio 2015 Licensing Guide”. Você pode baixa-lo no site da Microsoft ou diretamente aqui no meu site. Renomeie esse arquivo para “VS2015Licensing.pdf” e coloque-o dentro do diretório “bin/debug” da aplicação.
Opção 1: Process.Start
A primeira opção é a mais simples de todas. Basta utilizarmos a classe “Process” para iniciarmos um processo passando o caminho do PDF. Isso fará com que o visualizador padrão de PDFs seja aberto exibindo o arquivo que passamos para ele:
' VB.NET
Private Sub processStartButton_Click(sender As Object, e As EventArgs) Handles processStartButton.Click
System.Diagnostics.Process.Start("VS2015Licensing.pdf")
End Sub
Uma desvantagem obvia dessa alternativa é que obrigatoriamente precisamos ter um visualizador de PDFs instalado no nosso computador. A partir do Windows 8 o sistema operacional possui um visualizador de PDFs nativo. Apesar de ser bem difícil encontrar um computador sem o Adobe Reader, em versões anteriores do Windows, não conseguimos ter certeza se o usuário terá ou não um visualizador de PDFs instalado no seu computador. Além disso, como o PDF será exibido em uma aplicação externa, nós não temos nenhum controle sobre o processo.
Opção 2: WebBrowser.Navigate
Uma outra opção que podemos utilizar para exibirmos PDF no Windows Forms é adicionarmos um controle WebBrowser no nosso formulário e fazermos a navegação até o caminho onde o PDF está armazenado. Para demonstrar essa alternativa, vamos adicionar um novo formulário ao nosso projeto, dando o nome de “FormWebBrowser“. Dentro desse formulário, vamos adicionar um controle WebBrowser, ajustando também a sua propriedade “Dock” para “Fill“, de forma que ele ocupe todo o espaço do formulário.
Feito isso, no code-behind do formulário, vamos adicionar o código para exibirmos o PDF:
// C#
public FormWebBrowser()
{
InitializeComponent();
webBrowser.Navigate(string.Format(@"file://{0}\VS2015Licensing.pdf", Application.StartupPath));
}
' VB.NET
Private Sub FormWebBrowser_Load(sender As Object, e As EventArgs) Handles MyBase.Load
WebBrowser.Navigate(String.Format("file://{0}\VS2015Licensing.pdf", Application.StartupPath))
End Sub
Em seguida, vamos adicionar o código para abrirmos esse formulário ao clicarmos no botão “WebBrowser” do formulário principal:
// C#
private void webBrowserButton_Click(object sender, EventArgs e)
{
using (var formWebBrowser = new FormWebBrowser())
{
formWebBrowser.ShowDialog();
}
}
' VB.NET
Private Sub webBrowserButton_Click(sender As Object, e As EventArgs) Handles webBrowserButton.Click
Using FormWebBrowser = New FormWebBrowser()
FormWebBrowser.ShowDialog()
End Using
End Sub
Execute a aplicação e veja o resultado:
Essa alternativa é um pouco melhor do que a primeira, uma vez que o PDF será exibido diretamente no nosso aplicativo. Porém, ela depende que o Adobe Reader esteja instalado no computador. Além disso, como ela utiliza a engine do Internet Explorer, ela pode acabar não funcionando corretamente dependendo das configurações de segurança do browser e do sistema operacional.
Opção 3: Componente COM da Adobe
A terceira alternativa para exibirmos PDFs nas nossas aplicações Windows Forms é utilizarmos o componente COM do Adobe Reader. Se o Adobe Reader tiver sido instalado no computador cliente, nós teremos acesso a esse componente COM através da nossa aplicação. Para demonstrar essa opção, vamos criar um terceiro formulário no nosso projeto, dando o nome de “FormComponenteCOM“.
Uma vez adicionado o formulário, vamos até a barra de ferramentas do Visual Studio, clicamos com o botão direito e escolhemos a opção “Choose Items“:
Na tela “Choose Toolbox Items“, dentro da categoria “COM Components“, temos que encontrar e marcar o item “Adobe PDF Reader” para que ele apareça na caixa de ferramentas:
Agora é só arrastar esse componente para dentro do formulário e fazer um docking para que ele ocupe o espaço todo do formulário. Depois, no code-behind, fazemos o carregamento do PDF:
// C#
public FormComponenteCOM()
{
InitializeComponent();
axAcroPDF1.LoadFile("VS2015Licensing.pdf");
}
' VB.NET
Private Sub FormComponenteCOM_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AxAcroPDF1.LoadFile("VS2015Licensing.pdf")
End Sub
Não podemos esquecer também de chamar um “Dispose” no componente durante o fechamento do formulário. Se não fizermos isso, o Adobe Reader continuará em execução mesmo depois de fecharmos o nosso formulário / aplicativo:
' VB.NET
Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
MyBase.OnFormClosing(e)
AxAcroPDF1.Dispose()
End Sub
Por fim, vamos implementar o evento “Click” do botão “Componente COM” no formulário principal, de forma que ele abra uma nova instância do nosso formulário com o componente COM:
// C#
private void componenteCOMButton_Click(object sender, EventArgs e)
{
using (var formComponenteCOM = new FormComponenteCOM())
{
formComponenteCOM.ShowDialog();
}
}
' VB.NET
Private Sub componenteCOMButton_Click(sender As Object, e As EventArgs) Handles componenteCOMButton.Click
Using FormComponenteCOM = New FormComponenteCOM()
FormComponenteCOM.ShowDialog()
End Using
End Sub
Veja só o resultado:
A desvantagem dessa alternativa continua sendo que ela só funcionará se o usuário tiver o Adobe Reader instalado no computador cliente.
Opção 4: Suítes de controles
Todas as opções apresentadas até agora dependem do Adobe Reader instalado no computador onde a aplicação será executada. E se quisermos uma alternativa que funcione mesmo se o usuário não tiver o Adobe Reader instalado? Bom, nesse caso temos algumas alternativas. A primeira delas é verificar se a sua empresa não utiliza uma suíte de componentes que possui um controle visualizador de PDFs.
As principais empresas que oferecem suítes de componentes têm um visualizadores de PDFs incluído no pacote do Windows Forms. Por exemplo, a DevExpress, Telerik e SyncFusion disponibilizam visualizadores de PDFs em suas suítes de controles. Se a sua empresa tiver a licença de alguma dessas suítes, nem pense duas vezes e utilize os controles disponibilizados por elas.
Opção 5: Controles pagos
Além dos visualizadores de PDFs presentes nas principais suítes de controles, temos também algumas alternativas de controles específicos para a visualização de PDFs. A vantagem é que eles são mais baratos e possuem mais funcionalidades. Todos esses controles possuem uma versão de avaliação, então, é só baixar, testar e ver qual que se adequa melhor às suas necessidades:
Na empresa onde eu trabalho, depois de termos avaliado vários componentes pagos para visualização de PDFs, acabamos escolhendo o PDF4NET. Ele tem atendido muito bem às nossas necessidades, tanto em questão de performance quanto em questão de funcionalidades.
Um outro componente pago que eu não listei ali em cima foi o Apitron. A razão de eu não ter listado ele junto com os outros componentes é a verdadeira sacanagem de marketing que eles fizeram com o componente Windows Forms deles. Veja só este artigo intitulado “Free .NET PDF Viewer control for Windows Forms developers“. A impressão que temos é que eles estão oferecendo esse controle gratuitamente, mas, ao analisarmos melhor os detalhes, somente o controle é gratuito, porém, a engine que faz a renderização do PDF é paga. Ou seja, você baixa o controle achando que é gratuito, mas, na verdade não é. Uma empresa que age com esse tipo de atitude hoje em dia só pode estar de brincadeira, não é mesmo?
Opção 6: Se matar com algum controle gratuito
Por fim, se você quiser se aventurar com bibliotecas e exemplos gratuitos que envolvem trocentos tipos de componentes open source, eu vou listar algumas opções:
– MoonPdf: controle open source visualizador de PDFs para WPF. Teoricamente poderíamos utilizar um wrapper do WPF no Windows Forms para fazermos uso desse controle, porém, não consegui fazer nem a versão WPF compilar.
– PDFViewerNET: projeto com um visualizador de PDF Windows Forms já compilado. Funciona, porém, não consegui encontrar o código fonte e, ao referenciar a dll já compilada em um outro projeto, dá erro na hora de executar a aplicação. [Edit: O José de Arimatéia encontrou o link para o código fonte do PDFViewerNET e postou logo abaixo nos comentários]
– PDF Viewer Control Without Acrobat Reader Installed: exemplo hospedado no CodeProject que utiliza uma pancada de outras bibliotecas. Funciona somente se estiver compilado em x86. Dando uma olhada no código, imagino que dê bastante trabalho integrar esse exemplo em uma outra aplicação.
– PDFSharp Preview Sample: exemplo utilizando a biblioteca PDFSharp onde a imagem de uma página do documento PDF é renderizada dentro de um Graphics (que posteriormente pode ser exibido em um PictureBox). Se a representação em imagem das páginas do PDF é o suficiente para você (sem interação, cliques em links, navegação, etc), sugiro que você dê uma olhada nesse exemplo.
Concluindo
Neste artigo você conferiu diversas maneiras de exibirmos PDFs no Windows Forms. Eu mostrei para você desde as opções mais simples (que requerem o Adobe Reader instalado no computador cliente) até opções mais robustas (que funcionam mesmo se o usuário não tiver o Adobe Reader instalado).
Dentre todas essas opções, as minhas preferidas são a opção 3 (componente COM da Adobe, caso tenhamos certeza que o Adobe Reader esteja instalado no computador cliente e desde que segurança não seja um requisito imprescindível – afinal de contas, nesse caso estamos praticamente executando o Adobe Reader dentro da nossa aplicação) e a opção 4 (se você ainda não estiver utilizando uma suíte de controles nas sua aplicação, eu recomendo que você faça esse investimento agora mesmo).
Na empresa onde eu trabalho nós só utilizamos o PDF4NET porque, na época, a DevExpress ainda não tinha um controle visualizador de PDFs para o Windows Forms. Como nós temos a suíte de controles da DevExpress, se fossemos desenvolver essa parte do aplicativo hoje em dia, com certeza utilizaríamos o próprio controle de PDF da DevExpress.
E você? Já precisou exibir PDFs nas suas aplicações Windows Forms? Se sim, qual dessas metodologias você utilizou? E por que você acabou escolhendo essa opção? Ficamos aguardando as suas observações na caixa de comentários logo abaixo do artigo.
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.
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")
– 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:
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.
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.
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.
Uma das perguntas que encontramos frequentemente nos fóruns da MSDN é sobre a geração de arquivos do Office na plataforma de desenvolvimento do .NET Framework. Por ser uma funcionalidade muito específica, o .NET Framework não traz nativamente classes que possibilitam a manipulação de arquivos do Office. Nesse caso, a única chance que temos para gerarmos arquivos do Word com C# e VB.NET (ou outros tipos de arquivos da suíte do Office) é a utilização de bibliotecas externas.
O que muita gente não sabe é que existem inúmeras opções para trabalharmos com arquivos do Office no .NET. Neste artigo eu vou apresentar para você 4 opções para gerarmos arquivos do Word com C# e VB.NET. Como o assunto é bastante extenso, resolvi abordar somente arquivos do Word neste artigo. Em um próximo artigo eu mostro a mesma funcionalidade para arquivos do Excel.
Antes de abordarmos cada uma das opções, vamos construir um documento de exemplo. Pensando nas principais funcionalidades que utilizamos em arquivos do Word, preparei este documento:
Como você pode ver, nesse documento temos um parágrafo centralizado, outro parágrafo com uma fonte diferente da padrão, outro parágrafo com formatações diferentes dentro da mesma frase, uma tabela e uma imagem. Acho que ao conseguirmos construir um documento com essas funcionalidades, teremos uma boa ideia de como utilizamos cada uma das bibliotecas que serão apresentadas. Você pode baixar o documento de exemplo através deste link.
Além disso, para estudarmos cada uma das opções, vamos preparar um projeto do tipo “Windows Forms Application” com um formulário contendo 4 botões, um para cada biblioteca:
Opção 1: Office Automation
A primeira e mais simples opção que podemos utilizar para gerarmos arquivos do Word no C# é o Office Automation. Basicamente isso quer dizer que vamos utilizar as dlls de interoperabilidade (Interop) do Office para o .NET Framework. Essas bibliotecas possibilitam a operação das aplicações do Office através dos nossos aplicativos .NET.
O primeiro passo que temos que seguir nesse caso é adicionarmos a referência à dll de interoperabilidade do Word. Essa dll encontra-se dentro da categoria “Extensions” da tela “Reference Manager“:
Como você pode perceber, cada aplicativo do Office tem a sua própria dll de interop. Nesse caso adicionamos a biblioteca do Word, mas, se você fosse gerar um arquivo do Excel, você teria que, obviamente, adicionar a dll de interoperabilidade do Excel.
Com a dll adicionada, vamos dar um duplo clique no botão “Office Automation” para começarmos a implementar o código de geração do documento. A primeira coisa que temos que fazer é criarmos uma instância da classe “Word.Application“:
// C#
var wordApp = new Microsoft.Office.Interop.Word.Application();
wordApp.Visible = false;
var wordDoc = wordApp.Documents.Add();
' VB.NET
Dim WordApp As New Microsoft.Office.Interop.Word.Application()
WordApp.Visible = False
Dim WordDoc = WordApp.Documents.Add()
O que estamos fazendo no código acima é simplesmente abrindo uma instância do Word, configurando-o para trabalhar em segundo plano (propriedade Visible = false) e criando um novo documento. Em seguida, temos que adicionar cada um dos parágrafos no nosso documento.
Como vimos no documento de exemplo, o primeiro parágrafo contém somente um texto simples com alinhamento centralizado. Veja como fica o código para gerar esse parágrafo:
Fácil, não? Basta adicionarmos um parágrafo no documento, setarmos o texto do “Range” (que representa o conteúdo do parágrafo) e configurarmos o alinhamento para centralizado.
Em seguida, temos o parágrafo com fonte diferente. Adicionamos esse parágrafo da mesma forma que fizemos com o parágrafo anterior, porém, dessa vez alteraremos a propriedade “Font” do “Range“:
Feito isso, vamos ao parágrafo que contém a formatação mais complicada. Nesse próximo parágrafo configuraremos uma formatação diferente para algumas partes do texto. Essa é uma parte um pouco chata de fazermos com a dll de interop do Word. Como estamos praticamente operando o Word através da nossa aplicação, para conseguirmos aplicar uma formatação diferente para partes do texto, teremos que basicamente “digitar” o texto e selecionarmos cada uma das partes que queremos aplicar a formatação para alterarmos a propriedade desejada:
// C#
var paragrafo3 = wordDoc.Paragraphs.Add();
paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado";
paragrafo3.Range.Select();
wordApp.Selection.ClearFormatting();
wordApp.Selection.Collapse();
var rangeNegrito = wordDoc.Range(paragrafo3.Range.Start + 27, paragrafo3.Range.Start + 47);
rangeNegrito.Bold = 1;
var rangeSublinhado = wordDoc.Range(paragrafo3.Range.Start + 49);
rangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
paragrafo3.Range.InsertParagraphAfter();
' VB.NET
Dim Paragrafo3 = WordDoc.Paragraphs.Add()
Paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado"
Paragrafo3.Range.Select()
WordApp.Selection.ClearFormatting()
WordApp.Selection.Collapse()
Dim RangeNegrito = WordDoc.Range(Paragrafo3.Range.Start + 27, Paragrafo3.Range.Start + 47)
RangeNegrito.Bold = 1
Dim RangeSublinhado = WordDoc.Range(Paragrafo3.Range.Start + 49)
RangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
Paragrafo3.Range.InsertParagraphAfter()
Note que temos selecionar o texto utilizando o método “Range” do documento, passando os índices de início e final do texto que queremos aplicar a formatação. Não é o melhor dos códigos, mas, é a única maneira de aplicarmos formatações distintas no mesmo parágrafo com o Office Automation.
O próximo passo é criarmos mais um parágrafo, dessa vez contendo uma tabela com duas linhas e duas colunas:
Tranquilo, não? Só temos que adicionar a tabela no documento (Tables.Add), passando o número de linhas e colunas. Depois, configuramos o conteúdo de cada uma das células e definimos o estilo de borda da tabela. Um único detalhe importante para quem utiliza o C# é que os índices das linhas e colunas começam no “1“, e não no “0” como estamos acostumados no C#.
Por fim, vamos ao último parágrafo, que deverá conter a imagem dos pinguins. É extremamente fácil adicionarmos uma imagem no documento com a dll de interoperabilidade do Word. Basta chamarmos o método AddPicture passando o caminho da imagem:
// C#
var paragrafo5 = wordDoc.Paragraphs.Add();
paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"));
' VB.NET
Dim Paragrafo5 = WordDoc.Paragraphs.Add()
Paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"))
Só não esqueça de copiar o arquivo penguins.jpg para o diretório da aplicação.
O último passo que temos que seguir para fecharmos a geração do arquivo “doc” com as dlls de interop do Office é salvarmos o arquivo e fecharmos o Microsoft Word que estava rodando em segundo plano. Para isso, basta utilizarmos os métodos “SaveAs” e “Quit“. Nesse caso, você pode utilizar tanto o formato “doc” quanto o formato “docx“:
Note que temos que suprimir os alertas do Microsoft Word, senão a aplicação rodando em segundo plano pode mostrar janelas de mensagens em alguns casos.
E com isso fechamos a geração do documento utilizando Office Automation (interop). Veja só o código completo do clique do botão “Office Automation“:
// C#
private void officeAutomationButton_Click(object sender, EventArgs e)
{
var wordApp = new Microsoft.Office.Interop.Word.Application();
wordApp.Visible = false;
var wordDoc = wordApp.Documents.Add();
// Primeiro parágrafo (texto centralizado)
var paragrafo1 = wordDoc.Content.Paragraphs.Add();
paragrafo1.Range.Text = "Texto centralizado";
paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter;
paragrafo1.Range.InsertParagraphAfter();
// Segundo parágrafo (formatação diferente)
var paragrafo2 = wordDoc.Paragraphs.Add();
paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18";
paragrafo2.Range.Font.Name = "Arial";
paragrafo2.Range.Font.Bold = 1;
paragrafo2.Range.Font.Italic = 1;
paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
paragrafo2.Range.Font.Size = 18;
paragrafo2.Range.InsertParagraphAfter();
// Terceiro parágrafo (multiplas formatações)
var paragrafo3 = wordDoc.Paragraphs.Add();
paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado";
paragrafo3.Range.Select();
wordApp.Selection.ClearFormatting();
wordApp.Selection.Collapse();
var rangeNegrito = wordDoc.Range(paragrafo3.Range.Start + 27, paragrafo3.Range.Start + 47);
rangeNegrito.Bold = 1;
var rangeSublinhado = wordDoc.Range(paragrafo3.Range.Start + 49);
rangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle;
paragrafo3.Range.InsertParagraphAfter();
// Quarto parágrafo (tabela)
var paragrafo4 = wordDoc.Paragraphs.Add();
var tabela = wordDoc.Tables.Add(paragrafo4.Range, 2, 2);
tabela.Rows[1].Cells[1].Range.Text = "Col1";
tabela.Rows[1].Cells[2].Range.Text = "Col2";
tabela.Rows[2].Cells[1].Range.Text = "A";
tabela.Rows[2].Cells[2].Range.Text = "B";
tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle;
tabela.Select();
wordApp.Selection.ClearFormatting();
wordApp.Selection.Collapse();
paragrafo4.Range.InsertParagraphAfter();
// Quinto parágrafo (imagem)
var paragrafo5 = wordDoc.Paragraphs.Add();
paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"));
wordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"));
wordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone;
wordApp.Quit();
}
' VB.NET
Private Sub officeAutomationButton_Click(sender As Object, e As EventArgs) Handles officeAutomationButton.Click
Dim WordApp As New Microsoft.Office.Interop.Word.Application()
WordApp.Visible = False
Dim WordDoc = WordApp.Documents.Add()
' Primeiro parágrafo (texto centralizado)
Dim Paragrafo1 = WordDoc.Content.Paragraphs.Add()
Paragrafo1.Range.Text = "Texto centralizado"
Paragrafo1.Range.ParagraphFormat.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphCenter
Paragrafo1.Range.InsertParagraphAfter()
' Segundo parágrafo (formatação diferente)
Dim Paragrafo2 = WordDoc.Paragraphs.Add()
Paragrafo2.Range.Text = "Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"
Paragrafo2.Range.Font.Name = "Arial"
Paragrafo2.Range.Font.Bold = 1
Paragrafo2.Range.Font.Italic = 1
Paragrafo2.Range.Font.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
Paragrafo2.Range.Font.Size = 18
Paragrafo2.Range.InsertParagraphAfter()
' Terceiro parágrafo (multiplas formatações)
Dim Paragrafo3 = WordDoc.Paragraphs.Add()
Paragrafo3.Range.Text = "Um pedaço da frase normal, outro pedaço negrito, outro sublinhado"
Paragrafo3.Range.Select()
WordApp.Selection.ClearFormatting()
WordApp.Selection.Collapse()
Dim RangeNegrito = WordDoc.Range(Paragrafo3.Range.Start + 27, Paragrafo3.Range.Start + 47)
RangeNegrito.Bold = 1
Dim RangeSublinhado = WordDoc.Range(Paragrafo3.Range.Start + 49)
RangeSublinhado.Underline = Microsoft.Office.Interop.Word.WdUnderline.wdUnderlineSingle
Paragrafo3.Range.InsertParagraphAfter()
' Quarto parágrafo (tabela)
Dim Paragrafo4 = WordDoc.Paragraphs.Add()
Dim Tabela = WordDoc.Tables.Add(Paragrafo4.Range, 2, 2)
Tabela.Rows(1).Cells(1).Range.Text = "Col1"
Tabela.Rows(1).Cells(2).Range.Text = "Col2"
Tabela.Rows(2).Cells(1).Range.Text = "A"
Tabela.Rows(2).Cells(2).Range.Text = "B"
Tabela.Borders.InsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
Tabela.Borders.OutsideLineStyle = Microsoft.Office.Interop.Word.WdLineStyle.wdLineStyleSingle
Tabela.Select()
WordApp.Selection.ClearFormatting()
WordApp.Selection.Collapse()
Paragrafo4.Range.InsertParagraphAfter()
' Quinto parágrafo (imagem)
Dim Paragrafo5 = WordDoc.Paragraphs.Add()
Paragrafo5.Range.InlineShapes.AddPicture(System.IO.Path.Combine(Application.StartupPath, "penguins.jpg"))
WordDoc.SaveAs2(System.IO.Path.Combine(Application.StartupPath, "documentoAutomation.docx"))
WordApp.DisplayAlerts = Microsoft.Office.Interop.Word.WdAlertLevel.wdAlertsNone
WordApp.Quit()
End Sub
E aqui você confere o resultado:
Vale salientar que esse código só funcionará caso o Microsoft Word esteja instalado no computador onde a aplicação estiver sendo executada. Nas próximas alternativas nós veremos como gerar arquivos do Word sem que o Office esteja instalado no computador destino.
Opção 2: Aspose (biblioteca comercial)
A próxima alternativa que iremos analisar é a utilização da biblioteca comercial Aspose.Words. No momento da escrita desse artigo (maio de 2016), a licença dessa biblioteca está custando 999 dólares. Com essa biblioteca conseguimos gerar arquivos “doc” e “docx” sem que o Office esteja instalado no computador destino.
Para analisarmos a geração de arquivos com o Aspose, baixe o trial da biblioteca no site oficial. Uma vez baixada e descompactada, adicione a referência à dll “Aspose.Words.dll” no projeto da aplicação:
Com isso, podemos começar a desenvolver o código para a criação do arquivo “doc” com o Aspose. O primeiro passo é criarmos uma instância de Words.Document e Words.DocumentBuilder, além de configurarmos a fonte para “Calibri” no DocumentBuilder (o nosso documento de exemplo utiliza a fonte “Calibri“, mas, a fonte padrão do Aspose é a “Arial“):
// C#
var wordDoc = new Aspose.Words.Document();
var builder = new Aspose.Words.DocumentBuilder(wordDoc);
builder.Font.Name = "Calibri";
' VB.NET
Dim WordDoc = New Aspose.Words.Document()
Dim Builder = New Aspose.Words.DocumentBuilder(WordDoc)
Builder.Font.Name = "Calibri"
Em seguida, vamos ver como é fácil criarmos o nosso primeiro parágrafo com texto centralizado, simplesmente configurando a propriedade ParagraphFormat.Alignment:
Depois do parágrafo centralizado, temos o parágrafo com fonte diferente (Arial, Negrito, Itálico, Sublinhado, Tamanho 18). Para alterarmos a fonte do que está sendo gerado pelo DocumentBuilder, basta alterarmos as informações da propriedade “Font“:
O terceiro parágrafo é aquele que contém diferentes formatações dentro do mesmo parágrafo. Para conseguirmos diferentes formatações com o Aspose, temos que alternar entre configurações da propriedade “Font” e chamadas do método “Write“. Basicamente, o método “Write” respeitará as configurações de fonte que foram estabelecidas antes da sua chamada. Dessa forma, o código para a criação do parágrafo com formatações diferentes ficaria assim:
No quarto parágrafo teremos a tabela com duas colunas e duas linhas. No Aspose, tabelas são criadas através do método “StartTable” do DocumentBuilder. Uma vez criada a tabela, temos que adicionar cada uma das células utilizando o método “InsertCell“. As células são criadas no sentido esquerda para direita, cima para baixo. Uma vez que criamos todas as células de uma linha da tabela, temos que chamar o método “EndRow” para começarmos a próxima linha da tabela. Veja só como fica o código:
O quinto e último parágrafo deverá conter a imagem “penguins.jpg“. No caso da biblioteca Aspose, a inserção de imagens em documentos é extremamente simples. Só temos que chamar o método “InsertImage” do DocumentBuilder passando o caminho da imagem:
// C#
var paragrafo5 = builder.InsertParagraph();
builder.InsertImage("penguins.jpg");
' VB.NET
Dim Paragrafo5 = Builder.InsertParagraph()
Builder.InsertImage("penguins.jpg")
Por fim, para salvarmos o arquivo, chamamos o método “Save” do documento. Podemos utilizar tanto o formato “doc” quanto o formato “docx“:
// C#
wordDoc.Save("documentoAspose.docx");
' VB.NET
WordDoc.Save("documentoAspose.docx")
Veja só como fica o código completo da geração do documento com a biblioteca Aspose:
' VB.NET
Private Sub asposeButton_Click(sender As Object, e As EventArgs) Handles asposeButton.Click
Dim WordDoc = New Aspose.Words.Document()
Dim Builder = New Aspose.Words.DocumentBuilder(WordDoc)
Builder.Font.Name = "Calibri"
' Primeiro parágrafo (texto centralizado)
Builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center
Builder.Write("Texto centralizado")
' Segundo parágrafo (formatação diferente)
Dim Paragrafo2 = Builder.InsertParagraph()
Builder.Font.Name = "Arial"
Builder.Font.Bold = True
Builder.Font.Italic = True
Builder.Font.Underline = Aspose.Words.Underline.Single
Builder.Font.Size = 18
Builder.Write("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")
' Terceiro parágrafo (multiplas formatações)
Dim Paragrafo3 = Builder.InsertParagraph()
Builder.ParagraphFormat.ClearFormatting()
Builder.Font.ClearFormatting()
Builder.Font.Name = "Calibri"
Builder.Write("Um pedaço da frase normal, ")
Builder.Font.Bold = True
Builder.Write("outro pedaço negrito, ")
Builder.Font.Bold = False
Builder.Font.Underline = Aspose.Words.Underline.Single
Builder.Write("outro sublinhado")
' Quarto parágrafo (tabela)
Dim Paragrafo4 = Builder.InsertParagraph()
Builder.Font.ClearFormatting()
Builder.Font.Name = "Calibri"
Builder.StartTable()
Builder.InsertCell()
Builder.Write("Col1")
Builder.InsertCell()
Builder.Write("Col2")
Builder.EndRow()
Builder.InsertCell()
Builder.Write("A")
Builder.InsertCell()
Builder.Write("B")
Builder.EndRow()
Builder.EndTable()
' Quinto parágrafo (imagem)
Dim Paragrafo5 = Builder.InsertParagraph()
Builder.InsertImage("penguins.jpg")
WordDoc.Save("documentoAspose.docx")
End Sub
E o resultado final:
Bem mais simples, não é mesmo? A vantagem de utilizarmos a biblioteca Aspose é que o código fica bem mais simples, porém, o custo com certeza pode ser um complicador.
Opção 3: OpenXML SDK
A próxima alternativa que analisaremos para gerarmos arquivos do Word com C# e VB.NET é o SDK do OpenXML. Essa é, sem dúvida alguma, a maneira mais difícil de gerarmos arquivos do Word nas nossas aplicações .NET.
Não sei se você sabe, mas, os arquivos “docx” são na realidade um tipo de arquivo XML. O que o SDK do OpenXML possibilita é a criação desse XML de forma mais “fácil” (entre aspas, porque mesmo assim o processo é bem doloroso).
Para analisarmos a criação do documento com o SDK do OpenXML, a primeira coisa que temos que fazer é baixar o SDK e compilar. Você pode baixar o projeto do OpenXML SDK no GitHub. Se você preferir, eu já baixei, compilei e estou disponibilizando a dll aqui (versão de maio de 2016 – extraia a dll de dentro do arquivo zip).
Uma vez adicionada a referência à dll, temos que criar quatro objetos: um WordprocessingDocument, um MainDocumentPart, um Wordprocessing.Document e um Wordprocessing.Body (já deu para sentir que o nível de dificuldade aumentou consideravelmente?):
// C#
var wordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
var mainPart = wordDoc.AddMainDocumentPart();
var document = mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
var body = document.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Body());
' VB.NET
Dim WordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)
Dim MainPart = WordDoc.AddMainDocumentPart()
MainPart.Document = New DocumentFormat.OpenXml.Wordprocessing.Document()
Dim Document = MainPart.Document
Dim Body = Document.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Body())
Em seguida, vamos criar o primeiro parágrafo, com o texto centralizado:
// C#
var paragrafo1 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
paragrafo1.ParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(new DocumentFormat.OpenXml.Wordprocessing.Justification() { Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center });
var run = paragrafo1.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"));
' VB.NET
Dim Paragrafo1 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Paragrafo1.ParagraphProperties = New DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(New DocumentFormat.OpenXml.Wordprocessing.Justification() With {.Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center})
Dim Run = Paragrafo1.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"))
Note que a estratégia que temos que seguir aqui é criarmos um parágrafo, configurar suas propriedades, atachá-lo no “Body” e adicionarmos um “Run” dentro do parágrafo. Os “Runs” no contexto do OpenXML SDK nada mais são do que “textos“.
Seguindo a mesma lógica, vamos ao segundo parágrafo, onde temos uma formatação mais customizada, contendo uma fonte diferente (Arial), negrito, itálico, sublinhado e com tamanho diferente:
// C#
var paragrafo2 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
run = paragrafo2.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
var runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.RunFonts() { Ascii = "Arial" }));
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Italic() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.FontSize() { Val = new DocumentFormat.OpenXml.StringValue("36") });
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"));
' VB.NET
Dim Paragrafo2 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Run = Paragrafo2.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Dim RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.RunFonts() With {.Ascii = "Arial"}))
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Italic() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.FontSize() With {.Val = New DocumentFormat.OpenXml.StringValue("36")})
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"))
Um ponto muito importante a ser salientado com relação às fontes no OpenXML SDK é que elas são medidas em “meio ponto“. Ou seja, se quisermos configurar um texto com o tamanho “18” que estamos acostumados no Office, temos que, na verdade, configurar “36” no OpenXML SDK (multiplicar o valor desejado por dois).
O próximo parágrafo conterá o texto com formatações diferentes na mesma frase. Isso não é tão difícil de ser feito no OpenXML SDK. Nós só temos que criar “Runs” separadas para cada parte do texto, adicionando a formatação desejada em cada uma das partes:
// C#
var paragrafo3 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) }));
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single }));
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"));
' VB.NET
Dim Paragrafo3 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)}))
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single}))
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"))
Nesse caso, uma observação importante é que as “Runs” do OpenXML SDK não preservam espaços no começo e final da frase. No nosso exemplo, como queremos manter o espaço entre os textos de formatações diferentes, temos que indicar a propriedade “Space = Preserve” na “Run“.
OK, até aqui tudo certo. O código foi consideravelmente maior e mais complexo do que as outras opções, mas, nada catastrófico. Agora, vamos ver o quão complexo é adicionar uma tabela com o OpenXML SDK? Primeiro eu vou mostrar o código e depois eu comento:
// C#
var table = new DocumentFormat.OpenXml.Wordprocessing.Table();
var tableProperties = new DocumentFormat.OpenXml.Wordprocessing.TableProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("5000") });
tableProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.TableBorders(
new DocumentFormat.OpenXml.Wordprocessing.TopBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.BottomBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.LeftBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.RightBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
}));
table.AppendChild(tableProperties);
var tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
var tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
table.Append(tr);
tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("A"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("B"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
table.Append(tr);
body.AppendChild(table);
' VB.NET
Dim Table = New DocumentFormat.OpenXml.Wordprocessing.Table()
Dim TableProperties = New DocumentFormat.OpenXml.Wordprocessing.TableProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("5000")})
TableProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.TableBorders(
New DocumentFormat.OpenXml.Wordprocessing.TopBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.BottomBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.LeftBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.RightBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
}))
Table.AppendChild(TableProperties)
Dim Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
Dim Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Table.Append(Tr)
Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("A"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("B"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Table.Append(Tr)
Body.AppendChild(Table)
Uau! Essa doeu, hein! Primeiro temos um blocão de código para configurarmos as bordas da tabela (o padrão das tabelas no OpenXML SDK é sem bordas). Não existe uma propriedade de borda “geral“, ou seja, temos que configurar cada um dos tipos de borda (superior, inferior, direita, esquerda, interior horizontal e interior vertical). Depois, para cada linha da tabela temos que criar um TableRow. Para cada célula temos que criar um TableCell, um Paragraph e um Run. Se não bastasse isso, temos ainda que configurar a largura de cada uma das células (que no nosso caso, queremos 50% do tamanho da folha para cada coluna). E, para fechar com chave de ouro, o valor da largura percentual é configurado na medida de quinquagésimos de percentual! Ou seja, para 50%, temos que multiplicar 50×50 (daí o valor “2500“).
Você acha que depois dessa dificuldade para criar a tabela, o código não poderia ficar mais complicado, não é mesmo? Pois se você está de pé, é melhor se sentar. Eu vou mostrar agora o quão difícil é adicionar uma imagem no documento. Para você ter uma ideia, tem um artigo inteiro no MSDN só explicando isso.
Primeiro, precisamos de um método chamado “AddImageToBody“. Você pode pegar o código dele ali naquele artigo que eu linkei acima. Para facilitar a sua vida, eu vou colocar ele aqui também:
// C#
private static void AddImageToBody(DocumentFormat.OpenXml.Packaging.WordprocessingDocument wordDoc, string relationshipId)
{
// Define the reference of the image.
var element =
new DocumentFormat.OpenXml.Wordprocessing.Drawing(
new DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline(
new DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent() { Cx = 990000L, Cy = 792000L },
new DocumentFormat.OpenXml.Drawing.Wordprocessing.EffectExtent()
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
new DocumentFormat.OpenXml.Drawing.Wordprocessing.DocProperties()
{
Id = (DocumentFormat.OpenXml.UInt32Value)1U,
Name = "Picture 1"
},
new DocumentFormat.OpenXml.Drawing.Wordprocessing.NonVisualGraphicFrameDrawingProperties(
new DocumentFormat.OpenXml.Drawing.GraphicFrameLocks() { NoChangeAspect = true }),
new DocumentFormat.OpenXml.Drawing.Graphic(
new DocumentFormat.OpenXml.Drawing.GraphicData(
new DocumentFormat.OpenXml.Drawing.Pictures.Picture(
new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties(
new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties()
{
Id = (DocumentFormat.OpenXml.UInt32Value)0U,
Name = "New Bitmap Image.jpg"
},
new DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties()),
new DocumentFormat.OpenXml.Drawing.Pictures.BlipFill(
new DocumentFormat.OpenXml.Drawing.Blip(
new DocumentFormat.OpenXml.Drawing.BlipExtensionList(
new DocumentFormat.OpenXml.Drawing.BlipExtension()
{
Uri =
"{28A0092B-C50C-407E-A947-70E740481C1C}"
})
)
{
Embed = relationshipId,
CompressionState =
DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print
},
new DocumentFormat.OpenXml.Drawing.Stretch(
new DocumentFormat.OpenXml.Drawing.FillRectangle())),
new DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties(
new DocumentFormat.OpenXml.Drawing.Transform2D(
new DocumentFormat.OpenXml.Drawing.Offset() { X = 0L, Y = 0L },
new DocumentFormat.OpenXml.Drawing.Extents() { Cx = 990000L, Cy = 792000L }),
new DocumentFormat.OpenXml.Drawing.PresetGeometry(
new DocumentFormat.OpenXml.Drawing.AdjustValueList()
) { Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle }))
) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" })
)
{
DistanceFromTop = (DocumentFormat.OpenXml.UInt32Value)0U,
DistanceFromBottom = (DocumentFormat.OpenXml.UInt32Value)0U,
DistanceFromLeft = (DocumentFormat.OpenXml.UInt32Value)0U,
DistanceFromRight = (DocumentFormat.OpenXml.UInt32Value)0U,
EditId = "50D07946"
});
// Append the reference to body, the element should be in a Run.
wordDoc.MainDocumentPart.Document.Body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(new DocumentFormat.OpenXml.Wordprocessing.Run(element)));
}
' VB.NET
Private Shared Sub AddImageToBody(ByVal wordDoc As DocumentFormat.OpenXml.Packaging.WordprocessingDocument, ByVal relationshipId As String)
' Define the reference of the image.
Dim element = New DocumentFormat.OpenXml.Wordprocessing.Drawing( _
New DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline( _
New DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent() With {.Cx = 990000L, .Cy = 792000L}, _
New DocumentFormat.OpenXml.Drawing.Wordprocessing.EffectExtent() With {.LeftEdge = 0L, .TopEdge = 0L, .RightEdge = 0L, .BottomEdge = 0L}, _
New DocumentFormat.OpenXml.Drawing.Wordprocessing.DocProperties() With {.Id = CType(1UI, DocumentFormat.OpenXml.UInt32Value), .Name = "Picture1"}, _
New DocumentFormat.OpenXml.Drawing.Wordprocessing.NonVisualGraphicFrameDrawingProperties( _
New DocumentFormat.OpenXml.Drawing.GraphicFrameLocks() With {.NoChangeAspect = True} _
), _
New DocumentFormat.OpenXml.Drawing.Graphic(New DocumentFormat.OpenXml.Drawing.GraphicData( _
New DocumentFormat.OpenXml.Wordprocessing.Picture( _
New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureProperties( _
New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualDrawingProperties() With {.Id = 0UI, .Name = "Koala.jpg"}, _
New DocumentFormat.OpenXml.Drawing.Pictures.NonVisualPictureDrawingProperties() _
), _
New DocumentFormat.OpenXml.Drawing.Pictures.BlipFill( _
New DocumentFormat.OpenXml.Drawing.Blip( _
New DocumentFormat.OpenXml.Drawing.BlipExtensionList( _
New DocumentFormat.OpenXml.Drawing.BlipExtension() With {.Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}"}) _
) With {.Embed = relationshipId, .CompressionState = DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print}, _
New DocumentFormat.OpenXml.Drawing.Stretch( _
New DocumentFormat.OpenXml.Drawing.FillRectangle() _
) _
), _
New DocumentFormat.OpenXml.Drawing.Pictures.ShapeProperties( _
New DocumentFormat.OpenXml.Drawing.Transform2D( _
New DocumentFormat.OpenXml.Drawing.Offset() With {.X = 0L, .Y = 0L}, _
New DocumentFormat.OpenXml.Drawing.Extents() With {.Cx = 990000L, .Cy = 792000L}), _
New DocumentFormat.OpenXml.Drawing.PresetGeometry( _
New DocumentFormat.OpenXml.Drawing.AdjustValueList() _
) With {.Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle} _
) _
) _
) With {.Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture"} _
) _
) With {.DistanceFromTop = 0UI, _
.DistanceFromBottom = 0UI, _
.DistanceFromLeft = 0UI, _
.DistanceFromRight = 0UI} _
)
' Append the reference to body, the element should be in a Run.
wordDoc.MainDocumentPart.Document.Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(New DocumentFormat.OpenXml.Wordprocessing.Run(element)))
End Sub
Nem tente entender o que está sendo feito nesse código (eu não consegui). Apenas aceite que ele funciona.
Por fim, uma vez tendo esse método, fica fácil adicionarmos uma imagem no documento. São só essas 4 linhas de código (5 no caso do VB.NET):
// C#
var imagePart = mainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg);
using (var stream = new System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open))
imagePart.FeedData(stream);
AddImageToBody(wordDoc, mainPart.GetIdOfPart(imagePart));
' VB.NET
Dim ImagePart = MainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg)
Using Stream As New System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open)
ImagePart.FeedData(Stream)
End Using
AddImageToBody(WordDoc, MainPart.GetIdOfPart(ImagePart))
E com isso finalizamos a criação do nosso documento com o OpenXML SDK. Agora só temos que salvar e fechar o arquivo:
// C#
wordDoc.Save();
wordDoc.Close();
' VB.NET
WordDoc.Save()
WordDoc.Close()
Se você se perdeu em um dos passos, não tem problema. Aqui vai o código completo do método para gerar o documento com o OpenXML SDK:
// C#
private void openXmlSdkButton_Click(object sender, EventArgs e)
{
var wordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
var mainPart = wordDoc.AddMainDocumentPart();
var document = mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
var body = document.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Body());
// Primeiro parágrafo (texto centralizado)
var paragrafo1 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
paragrafo1.ParagraphProperties = new DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(new DocumentFormat.OpenXml.Wordprocessing.Justification() { Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center });
var run = paragrafo1.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"));
// Segundo parágrafo (formatação diferente)
var paragrafo2 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
run = paragrafo2.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
var runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.RunFonts() { Ascii = "Arial" }));
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Italic() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single });
runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.FontSize() { Val = new DocumentFormat.OpenXml.StringValue("36") });
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"));
// Terceiro parágrafo (multiplas formatações)
var paragrafo3 = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Bold() { Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true) }));
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") { Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve });
run = paragrafo3.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
runProperties = run.PrependChild(new DocumentFormat.OpenXml.Wordprocessing.RunProperties(new DocumentFormat.OpenXml.Wordprocessing.Underline() { Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single }));
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"));
// Quarto parágrafo (tabela)
var table = new DocumentFormat.OpenXml.Wordprocessing.Table();
var tableProperties = new DocumentFormat.OpenXml.Wordprocessing.TableProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("5000") });
tableProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.TableBorders(
new DocumentFormat.OpenXml.Wordprocessing.TopBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.BottomBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.LeftBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.RightBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
},
new DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder
{
Val = new DocumentFormat.OpenXml.EnumValue<DocumentFormat.OpenXml.Wordprocessing.BorderValues>(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single),
}));
table.AppendChild(tableProperties);
var tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
var tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
table.Append(tr);
tr = new DocumentFormat.OpenXml.Wordprocessing.TableRow();
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("A"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
tc = new DocumentFormat.OpenXml.Wordprocessing.TableCell();
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.Paragraph(
new DocumentFormat.OpenXml.Wordprocessing.Run(
new DocumentFormat.OpenXml.Wordprocessing.Text("B"))));
tc.Append(new DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
new DocumentFormat.OpenXml.Wordprocessing.TableCellWidth { Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, Width = new DocumentFormat.OpenXml.StringValue("2500") }));
tr.Append(tc);
table.Append(tr);
body.AppendChild(table);
// Quinto parágrafo (imagem)
var imagePart = mainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg);
using (var stream = new System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open))
imagePart.FeedData(stream);
AddImageToBody(wordDoc, mainPart.GetIdOfPart(imagePart));
wordDoc.Save();
wordDoc.Close();
}
' VB.NET
Private Sub openXmlSdkButton_Click(sender As Object, e As EventArgs) Handles openXmlSdkButton.Click
Dim WordDoc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Create("documentoOpenXmlSdk.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document)
Dim MainPart = WordDoc.AddMainDocumentPart()
MainPart.Document = New DocumentFormat.OpenXml.Wordprocessing.Document()
Dim Document = MainPart.Document
Dim Body = Document.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Body())
' Primeiro parágrafo (texto centralizado)
Dim Paragrafo1 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Paragrafo1.ParagraphProperties = New DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties(New DocumentFormat.OpenXml.Wordprocessing.Justification() With {.Val = DocumentFormat.OpenXml.Wordprocessing.JustificationValues.Center})
Dim Run = Paragrafo1.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Texto centralizado"))
' Segundo parágrafo (formatação diferente)
Dim Paragrafo2 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Run = Paragrafo2.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Dim RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.RunFonts() With {.Ascii = "Arial"}))
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Italic() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single})
RunProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.FontSize() With {.Val = New DocumentFormat.OpenXml.StringValue("36")})
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18"))
' Terceiro parágrafo (multiplas formatações)
Dim Paragrafo3 = Body.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Paragraph())
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("Um pedaço da frase normal, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Bold() With {.Val = DocumentFormat.OpenXml.OnOffValue.FromBoolean(True)}))
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro pedaço negrito, ") With {.Space = DocumentFormat.OpenXml.SpaceProcessingModeValues.Preserve})
Run = Paragrafo3.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Run())
RunProperties = Run.PrependChild(New DocumentFormat.OpenXml.Wordprocessing.RunProperties(New DocumentFormat.OpenXml.Wordprocessing.Underline() With {.Val = DocumentFormat.OpenXml.Wordprocessing.UnderlineValues.Single}))
Run.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.Text("outro sublinhado"))
' Quarto parágrafo (tabela)
Dim Table = New DocumentFormat.OpenXml.Wordprocessing.Table()
Dim TableProperties = New DocumentFormat.OpenXml.Wordprocessing.TableProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("5000")})
TableProperties.AppendChild(New DocumentFormat.OpenXml.Wordprocessing.TableBorders(
New DocumentFormat.OpenXml.Wordprocessing.TopBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.BottomBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.LeftBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.RightBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.InsideHorizontalBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
},
New DocumentFormat.OpenXml.Wordprocessing.InsideVerticalBorder With
{
.Val = New DocumentFormat.OpenXml.EnumValue(Of DocumentFormat.OpenXml.Wordprocessing.BorderValues)(DocumentFormat.OpenXml.Wordprocessing.BorderValues.Single)
}))
Table.AppendChild(TableProperties)
Dim Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
Dim Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("Col1"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("Col2"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Table.Append(Tr)
Tr = New DocumentFormat.OpenXml.Wordprocessing.TableRow()
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("A"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Tc = New DocumentFormat.OpenXml.Wordprocessing.TableCell()
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.Paragraph(
New DocumentFormat.OpenXml.Wordprocessing.Run(
New DocumentFormat.OpenXml.Wordprocessing.Text("B"))))
Tc.Append(New DocumentFormat.OpenXml.Wordprocessing.TableCellProperties(
New DocumentFormat.OpenXml.Wordprocessing.TableCellWidth With {.Type = DocumentFormat.OpenXml.Wordprocessing.TableWidthUnitValues.Pct, .Width = New DocumentFormat.OpenXml.StringValue("2500")}))
Tr.Append(Tc)
Table.Append(Tr)
Body.AppendChild(Table)
' Quinto parágrafo (imagem)
Dim ImagePart = MainPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType.Jpeg)
Using Stream As New System.IO.FileStream("penguins.jpg", System.IO.FileMode.Open)
ImagePart.FeedData(Stream)
End Using
AddImageToBody(WordDoc, MainPart.GetIdOfPart(ImagePart))
WordDoc.Save()
WordDoc.Close()
End Sub
E aqui vai uma imagem do resultado obtido (note que mesmo com essa parafernalha toda, não consegui adicionar a imagem de forma que ela ocupasse a largura toda da página):
Opção 4: Biblioteca DocX
Agora que já estamos recuperados do susto que tomamos com o OpenXML SDK, quero apresentar para você a biblioteca DocX. Essa biblioteca é bem levinha e implementa basicamente a mesma coisa que o OpenXML SDK, só que de uma maneira muito mais fácil de ser utilizada. No final das contas, ela gera o XML correspondente ao DOCX e também não necessita da instalação do Office no computador cliente.
Para adicionar essa biblioteca no nosso projeto, procure por “docx” no NuGet e instale o pacote:
Uma vez adicionado o pacote no projeto, nós conseguiremos utilizar as suas funcionalidades, que se encontram no namespace “Novacode“. O primeiro passo é criarmos um bloco “using” chamando o método DocX.Create. Todo o código desse exemplo deverá ser colocado dentro desse bloco “using“:
// C#
using (var docX = Novacode.DocX.Create("documentoDocX.docx"))
{
}
' VB.NET
Using DocX = Novacode.DocX.Create("documentoDocX.docx")
End Using
Uma vez criado o bloco “using” vamos adicionar o código para criarmos o primeiro parágrafo, que deve ser centralizado:
Note que não tem segredo nenhum nesse caso. Basta inserirmos o parágrafo no documento, adicionarmos o texto dentro dele (método Append) e configurarmos o alinhamento do parágrafo através da propriedade Alignment. Um detalhe importante que temos que prestar atenção com a biblioteca DocX é que o espaçamento padrão dos parágrafos não é o que estamos acostumados com o Word. Portanto, sempre que criamos um parágrafo, temos que configurar o espaçamento para “8“.
Agora vamos continuar com o próximo parágrafo, que tem uma formatação um pouco mais elaborada:
Veja que as formatações na biblioteca DocX são feitas através de métodos. Ou seja, para fazer com que um parágrafo fique em negrito, temos que chamar o método Bold nesse parágrafo. O mesmo vale para o itálico, sublinhado e configurações de fonte.
E como é que fica o parágrafo com formatações diferentes dentro da mesma frase? Confira:
// C#
var paragrafo3 = docX.InsertParagraph();
paragrafo3.LineSpacingAfter = 8;
paragrafo3.Append("Um pedaço da frase normal, ");
var negrito = paragrafo3.Append("outro pedaço negrito, ");
negrito.Bold();
var sublinhado = paragrafo3.Append("outro sublinhado");
sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine);
' VB.NET
Dim Paragrafo3 = DocX.InsertParagraph()
Paragrafo3.LineSpacingAfter = 8
Paragrafo3.Append("Um pedaço da frase normal, ")
Dim Negrito = Paragrafo3.Append("outro pedaço negrito, ")
Negrito.Bold()
Dim Sublinhado = Paragrafo3.Append("outro sublinhado")
Sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine)
Observe que, quando chamamos o método Append para adicionar um texto no parágrafo, ele retorna uma variável com a parte do parágrafo que acabamos de adicionar. Dessa forma, para deixarmos somente aquela parte do parágrafo em negrito, basta chamarmos o método Bold na variável retornada.
Em seguida, vamos à tabela:
// C#
var paragrafo4 = docX.InsertParagraph();
paragrafo4.LineSpacingAfter = 8;
var tabela = paragrafo4.InsertTableAfterSelf(2, 2);
tabela.AutoFit = Novacode.AutoFit.Window;
tabela.Rows[0].Cells[0].InsertParagraph("Col1");
tabela.Rows[0].Cells[1].InsertParagraph("Col2");
tabela.Rows[1].Cells[0].InsertParagraph("A");
tabela.Rows[1].Cells[1].InsertParagraph("B");
tabela.SetBorder(Novacode.TableBorderType.Top, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Bottom, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Left, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Right, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.InsideH, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.InsideV, new Novacode.Border());
' VB.NET
Dim Paragrafo4 = DocX.InsertParagraph()
Paragrafo4.LineSpacingAfter = 8
Dim Tabela = Paragrafo4.InsertTableAfterSelf(2, 2)
Tabela.AutoFit = Novacode.AutoFit.Window
Tabela.Rows(0).Cells(0).InsertParagraph("Col1")
Tabela.Rows(0).Cells(1).InsertParagraph("Col2")
Tabela.Rows(1).Cells(0).InsertParagraph("A")
Tabela.Rows(1).Cells(1).InsertParagraph("B")
Tabela.SetBorder(Novacode.TableBorderType.Top, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Bottom, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Left, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Right, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.InsideH, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.InsideV, New Novacode.Border())
Como podemos observar no código acima, é muito fácil e intuitivo criarmos uma tabela com a biblioteca DocX.
Por fim, vamos adicionar a imagem no documento. Os passos para adicionarmos uma imagem com a biblioteca DocX são: 1) incluir a imagem no documento (método AddImage passando um MemoryStream com a representação do arquivo contendo a imagem), 2) chamar o método CreatePicture na imagem do documento e guardar o retorno em uma variável, 3) passar a imagem retornada pelo método CreatePicture como parâmetro para o método InsertPicture:
// C#
using (var memoryStream = new System.IO.MemoryStream())
{
var paragrafo5 = docX.InsertParagraph();
paragrafo5.LineSpacingAfter = 8;
var imagemDoArquivo = Image.FromFile("penguins.jpg");
imagemDoArquivo.Save(memoryStream, imagemDoArquivo.RawFormat);
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
var imagemNoDocumento = docX.AddImage(memoryStream);
var picture = imagemNoDocumento.CreatePicture();
var maxWidth = Convert.ToInt32(docX.PageWidth - docX.MarginLeft - docX.MarginRight);
if (picture.Width > maxWidth)
{
var ratio = (double)maxWidth / (double)picture.Width;
picture.Width = maxWidth;
picture.Height = Convert.ToInt32(picture.Height * ratio);
}
paragrafo5.InsertPicture(picture);
}
' VB.NET
Using MemoryStream As New System.IO.MemoryStream()
Dim Paragrafo5 = DocX.InsertParagraph()
Paragrafo5.LineSpacingAfter = 8
Dim ImagemDoArquivo = Image.FromFile("penguins.jpg")
ImagemDoArquivo.Save(MemoryStream, ImagemDoArquivo.RawFormat)
MemoryStream.Seek(0, System.IO.SeekOrigin.Begin)
Dim ImagemNoDocumento = DocX.AddImage(MemoryStream)
Dim Picture = ImagemNoDocumento.CreatePicture()
Dim MaxWidth = Convert.ToInt32(DocX.PageWidth - DocX.MarginLeft - DocX.MarginRight)
If (Picture.Width > MaxWidth) Then
Dim Ratio = CDbl(MaxWidth) / CDbl(Picture.Width)
Picture.Width = MaxWidth
Picture.Height = Convert.ToInt32(Picture.Height * Ratio)
End If
Paragrafo5.InsertPicture(Picture)
End Using
A única coisa que fiz a mais no bloco de código acima foi redimensionar a imagem para que ela ocupasse o tamanho todo da página (desconsiderando as margens). Como a biblioteca DocX adiciona a imagem no tamanho original, infelizmente temos que fazer esse ajuste manualmente.
Finalmente, vamos salvar o arquivo chamando o método Save:
// C#
docX.Save();
' VB.NET
DocX.Save()
Veja o código completo para a geração do documento com a biblioteca DocX:
// C#
private void docXButton_Click(object sender, EventArgs e)
{
using (var docX = Novacode.DocX.Create("documentoDocX.docx"))
{
// Primeiro parágrafo (texto centralizado)
var paragrafo1 = docX.InsertParagraph();
paragrafo1.LineSpacingAfter = 8;
paragrafo1.Append("Texto centralizado");
paragrafo1.Alignment = Novacode.Alignment.center;
// Segundo parágrafo (formatação diferente)
var paragrafo2 = docX.InsertParagraph();
paragrafo2.LineSpacingAfter = 8;
paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18");
paragrafo2.Font(new FontFamily("Arial"));
paragrafo2.FontSize(18);
paragrafo2.Bold();
paragrafo2.Italic();
paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine);
// Terceiro parágrafo (multiplas formatações)
var paragrafo3 = docX.InsertParagraph();
paragrafo3.LineSpacingAfter = 8;
paragrafo3.Append("Um pedaço da frase normal, ");
var negrito = paragrafo3.Append("outro pedaço negrito, ");
negrito.Bold();
var sublinhado = paragrafo3.Append("outro sublinhado");
sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine);
// Quarto parágrafo (tabela)
var paragrafo4 = docX.InsertParagraph();
paragrafo4.LineSpacingAfter = 8;
var tabela = paragrafo4.InsertTableAfterSelf(2, 2);
tabela.AutoFit = Novacode.AutoFit.Window;
tabela.Rows[0].Cells[0].InsertParagraph("Col1");
tabela.Rows[0].Cells[1].InsertParagraph("Col2");
tabela.Rows[1].Cells[0].InsertParagraph("A");
tabela.Rows[1].Cells[1].InsertParagraph("B");
tabela.SetBorder(Novacode.TableBorderType.Top, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Bottom, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Left, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.Right, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.InsideH, new Novacode.Border());
tabela.SetBorder(Novacode.TableBorderType.InsideV, new Novacode.Border());
// Quinto parágrafo (imagem)
using (var memoryStream = new System.IO.MemoryStream())
{
var paragrafo5 = docX.InsertParagraph();
paragrafo5.LineSpacingAfter = 8;
var imagemDoArquivo = Image.FromFile("penguins.jpg");
imagemDoArquivo.Save(memoryStream, imagemDoArquivo.RawFormat);
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
var imagemNoDocumento = docX.AddImage(memoryStream);
var picture = imagemNoDocumento.CreatePicture();
var maxWidth = Convert.ToInt32(docX.PageWidth - docX.MarginLeft - docX.MarginRight);
if (picture.Width > maxWidth)
{
var ratio = (double)maxWidth / (double)picture.Width;
picture.Width = maxWidth;
picture.Height = Convert.ToInt32(picture.Height * ratio);
}
paragrafo5.InsertPicture(picture);
}
docX.Save();
}
}
' VB.NET
Private Sub docXButton_Click(sender As Object, e As EventArgs) Handles docXButton.Click
Using DocX = Novacode.DocX.Create("documentoDocX.docx")
' Primeiro parágrafo (texto centralizado)
Dim Paragrafo1 = DocX.InsertParagraph()
Paragrafo1.LineSpacingAfter = 8
Paragrafo1.Append("Texto centralizado")
Paragrafo1.Alignment = Novacode.Alignment.center
' Segundo parágrafo (formatação diferente)
Dim Paragrafo2 = DocX.InsertParagraph()
Paragrafo2.LineSpacingAfter = 8
Paragrafo2.Append("Fonte Arial Negrito, Itálico, Sublinhado, Tamanho 18")
Paragrafo2.Font(New FontFamily("Arial"))
Paragrafo2.FontSize(18)
Paragrafo2.Bold()
Paragrafo2.Italic()
Paragrafo2.UnderlineStyle(Novacode.UnderlineStyle.singleLine)
' Terceiro parágrafo (multiplas formatações)
Dim Paragrafo3 = DocX.InsertParagraph()
Paragrafo3.LineSpacingAfter = 8
Paragrafo3.Append("Um pedaço da frase normal, ")
Dim Negrito = Paragrafo3.Append("outro pedaço negrito, ")
Negrito.Bold()
Dim Sublinhado = Paragrafo3.Append("outro sublinhado")
Sublinhado.UnderlineStyle(Novacode.UnderlineStyle.singleLine)
' Quarto parágrafo (tabela)
Dim Paragrafo4 = DocX.InsertParagraph()
Paragrafo4.LineSpacingAfter = 8
Dim Tabela = Paragrafo4.InsertTableAfterSelf(2, 2)
Tabela.AutoFit = Novacode.AutoFit.Window
Tabela.Rows(0).Cells(0).InsertParagraph("Col1")
Tabela.Rows(0).Cells(1).InsertParagraph("Col2")
Tabela.Rows(1).Cells(0).InsertParagraph("A")
Tabela.Rows(1).Cells(1).InsertParagraph("B")
Tabela.SetBorder(Novacode.TableBorderType.Top, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Bottom, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Left, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.Right, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.InsideH, New Novacode.Border())
Tabela.SetBorder(Novacode.TableBorderType.InsideV, New Novacode.Border())
' Quinto parágrafo (imagem)
Using MemoryStream As New System.IO.MemoryStream()
Dim Paragrafo5 = DocX.InsertParagraph()
Paragrafo5.LineSpacingAfter = 8
Dim ImagemDoArquivo = Image.FromFile("penguins.jpg")
ImagemDoArquivo.Save(MemoryStream, ImagemDoArquivo.RawFormat)
MemoryStream.Seek(0, System.IO.SeekOrigin.Begin)
Dim ImagemNoDocumento = DocX.AddImage(MemoryStream)
Dim Picture = ImagemNoDocumento.CreatePicture()
Dim MaxWidth = Convert.ToInt32(DocX.PageWidth - DocX.MarginLeft - DocX.MarginRight)
If (Picture.Width > MaxWidth) Then
Dim Ratio = CDbl(MaxWidth) / CDbl(Picture.Width)
Picture.Width = MaxWidth
Picture.Height = Convert.ToInt32(Picture.Height * Ratio)
End If
Paragrafo5.InsertPicture(Picture)
End Using
DocX.Save()
End Using
End Sub
E o resultado obtido:
Concluindo: qual opção eu devo escolher?
Muito bem, agora que já vimos os detalhes de cada uma dessas quatro opções que temos para gerar arquivos do Word com C# e VB.NET, qual opção devemos escolher? Qual dessas opções é a melhor? Bom, como tudo na vida, a resposta para essa pergunta depende do cenário da sua aplicação.
Cenário 1: arquivo “doc“, Office instalado
Se você obrigatoriamente precisa gerar um arquivo “doc” (formato do Office 97 e inferior) e você tem certeza que o Office estará instalado no computador cliente, a primeira opção (Office Automation / Office Interop) é a mais recomendada. A não ser que a sua empresa esteja disposta a gastar 1000 dólares com uma licença do Aspose.
Cenário 2: arquivo “doc“, Office não instalado
Por outro lado, se você obrigatoriamente precisa gerar um arquivo “doc” e não tiver certeza que o Office estará instalado no computador cliente, a única alternativa é partir para alguma biblioteca comercial. Dentre as que eu testei rapidamente, a Aspose foi a melhor que eu encontrei (além de ser a mais conhecida). Pode ser que você encontre alguma opção mais barata e que também atenda às suas necessidades. Se você encontrar alguma que tenha um custo x benefício melhor, avisa nos comentários.
Cenário 3: arquivo “docx”
Por fim, se você não tiver que obrigatoriamente gerar um arquivo “doc“, ou seja, se o tipo de arquivo “docx” for o suficiente, recomendo que você utilize a biblioteca DocX. Ela é gratuita, open source e implementa a maioria das funcionalidades existentes no Word.
Dentre as opções apresentadas nesse artigo, a única que eu realmente não recomendo é o OpenXML SDK. Apesar de ser extremamente completo (com todas as funcionalidades possíveis do formato “docx“), ele é muito difícil de ser utilizado, o que acaba não compensando a sua utilização se compararmos com a biblioteca DocX.
Enfim, espero que com o estudo apresentado nesse artigo você tenha aprendido de uma vez por todas a gerar arquivos do Word com C# e VB.NET. E você, já teve que fazer algo parecido? Qual dessas opções você utilizou? Conte-nos mais detalhes ali nos 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.
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):
' 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“:
' 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.
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:
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:
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:
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.
Imagine que você, programador .NET, está começando um novo projeto desktop e surge a seguinte dúvida: qual plataforma de desenvolvimento eu devo utilizar? Windows Forms ou WPF?
Não se assuste. Apesar dessas duas plataformas já estarem bem disseminadas no mercado (afinal de contas, a primeira versão do Windows Forms saiu em 2002 e a primeira versão do WPF saiu em 2006), essa dúvida é muito mais comum do que você pode imaginar.
Algumas pessoas já escreveram sobre esse assunto no passado como, por exemplo, o Giovanni Bassi neste excelente artigo: WPF ou Windows Forms. Porém, mesmo assim, eu quero dar a minha perspectiva nesse assunto polêmico, baseado na minha experiência trabalhando com ambas as plataformas desde 2005.
Antes de discutirmos qual tecnologia é melhor em cada cenário, vamos dar uma olhada nas principais características de cada uma delas.
Características do Windows Forms
É mais velho: a primeira versão do Windows Forms foi lançada junto com o .NET Framework, em 2002. Podemos dizer que o Windows Forms já está “pronto” e estável há muito tempo, uma vez que não tivemos novas funcionalidades para essa plataforma desde aproximadamente o lançamento do .NET Framework 3.0.
A curva de aprendizado é tranquila: mesmo para programadores iniciantes, a curva de aprendizado do Windows Forms é extremamente tranquila. Por ser muito “visual“, a facilidade de arrastar os controles para dentro da tela e implementar funcionalidades por trás deles é muito grande.
Baixa barreira para alta produtividade: como a curva de aprendizado é muito tranquila, com pouco tempo você atinge uma produtividade alta no desenvolvimento dos seus projetos.
Suporte à herança visual: a herança visual no Windows Forms funciona até que bem. Se você tem vários formulários muito parecidos no seu projeto, é muito fácil criar um formulário “base” que servirá de “pai” para todos os outros formulários.
Bastante documentação, artigos e conteúdo técnico: por ser mais velho e mais disseminado, é muito mais fácil encontrar documentação e artigos técnicos sobre o Windows Forms. Se você está querendo fazer alguma coisa um pouco diferente do padrão no Windows Forms, muito provavelmente alguém já fez no passado e acabou documentando em algum lugar. Se você souber procurar, você vai encontrar.
Vasta biblioteca de controles de terceiros: existe uma quantidade absurda de componentes de terceiros para o Windows Forms. As principais opções são DevExpress, Telerik, Infragistics e ComponentOne. Apesar do preço ser um pouco salgado, a gama de componentes disponibilizada por essas bibliotecas vale cada centavo que você paga.
Apresenta problemas quando mudamos o DPI: uma das dificuldades quando programamos com o Windows Forms é o problema com resoluções/DPIs diferentes. Com a evolução das placas gráficas e monitores, isso está se tornando um problema muito comum. Existem algumas maneiras de resolver esse problema (eu até já escrevi um artigo sobre isso), mas, não é tão fácil como no WPF.
Dificulta a utilização de uma arquitetura mais robusta: como mencionei anteriormente, o Windows Forms é muito “visual” e baseado em eventos. Apesar de isso trazer uma facilidade no desenvolvimento, por outro lado, acaba trazendo uma dificuldade se quisermos utilizar uma arquitetura mais robusta, onde separamos todo o código de negócio da camada de apresentação. É possível utilizar arquiteturas como o MVVM no Windows Forms (inclusive, nós utilizamos em um projeto bem grande na empresa onde eu trabalho atualmente), mas, não é tão simples como no WPF.
Está em “modo manutenção”: a Microsoft não tem trazido novas funcionalidades para o Windows Forms desde o .NET Framework 3.0. Ele está em “modo manutenção“, o que quer dizer que somente bugs serão corrigidos. No .NET Framework 4.5 a Microsoft corrigiu alguns problemas de DPI dos controles nativos, mas, nada de funcionalidades novas. Quando novas features são adicionadas ao sistema operacional, o Windows Forms muito provavelmente não trará suporte a elas e a única opção é recorrer a gambiarras se quisermos utilizá-las.
Características do WPF
É mais novo, mas, mesmo assim, já é “velho”: a primeira versão do WPF saiu junto com o .NET Framework 3.0, em 2006. Apesar de ser mais novo que o Windows Forms, se levarmos em conta que estamos em 2016, ele já tem 10 anos!
Curva de aprendizado maior (se utilizado do jeito “certo”): a dificuldade no aprendizado do WPF se você quiser utilizá-lo corretamente (por exemplo, com uma arquitetura robusta como o MVVM) é bem maior. Para utilizar o WPF na sua capacidade total, você precisa aprender conceitos de programação que não são tão triviais assim.
Também está, de certa forma, em “modo manutenção”: apesar de ser mais novo, costuma-se dizer que o WPF também está “pronto“. Ou seja, muito dificilmente a Microsoft trará novas funcionalidades ou novos componentes nas próximas versões do .NET Framework.
Menos conteúdo disponível na internet: não é tão fácil encontrar conteúdo sobre WPF na internet, principalmente em português. O conteúdo disponibilizado em inglês até que é compatível com os materiais sobre Windows Forms, mas, em português ele fica devendo.
Controles de terceiros têm qualidade inferior: se você comparar a quantidade de features de componentes de terceiros entre WPF e Windows Forms, você verá que a qualidade dos componentes da mesma empresa é inferior no WPF. Por exemplo, se você comparar qualquer controle da DevExpress entre o Windows Forms e WPF, verá que os componentes do WPF ainda não conseguiram chegar 100% nas funcionalidades existentes para o Windows Forms.
XAML e sistema de binding extraordinário: apesar de parecer um tanto quanto complexo de início, o XAML é uma linguagem muito poderosa. Ela também pode ser utilizada no desenvolvimento de aplicativos móveis com a Xamarin, então, se você aprender o XAML do WPF, será mais fácil trabalhar com o XAML no desenvolvimento Xamarin. Sem falar no sistema de binding do WPF que é, de longe, a melhor funcionalidade dessa plataforma.
Capacidades gráficas bem melhores: o WPF não trabalha com a unidade de pixels (como o Windows Forms), mas sim, algo chamado “device independent units“. Essa diferença faz com que os aplicativos WPF consigam “escalar” sem problemas em diferentes resoluções e DPIs. Além disso, o WPF traz nativamente o suporte a transformações geométricas, efeitos 3D, manipulação de arquivos de mídia, entre outros.
Facilita a criação de um código melhor arquitetado: a separação brusca entre o código de design (XAML) e o código de negócio (C# ou VB.NET), além do sistema de binding mencionado anteriormente, facilita a criação de um código bem arquitetado. A utilização de arquiteturas como o MVVM cai como uma luva no desenvolvimento de aplicações WPF. Essa separação de responsabilidades possibilita que uma pessoa do time trabalhe somente no design das janelas enquanto outra pessoa trabalhe na lógica de negócios.
Quando utilizar Windows Forms?
Agora que já vimos as principais características do Windows Forms e do WPF, vamos ver a minha opinião sobre quando utilizar uma plataforma e quando utilizar a outra plataforma. Porém, antes de começar, vamos a um pequeno “disclaimer“:
As informações que apresentarei a partir desse ponto são baseadas na minha opinião trabalhando com desenvolvimento de aplicações em ambas as plataformas desde 2005. Ou seja, isso não quer dizer que elas são 100% corretas. Como toda opinião, cada um pode ter a sua e podemos, sem problema algum, discordar em alguns pontos. Pegue essas informações e utilize-as da melhor forma, sempre adaptando para o cenário dos seus aplicativos.
Aplicações de complexidade básica/intermediária até intermediária/avançada que devem rodar somente em Windows: se a sua aplicação não tem uma complexidade extremamente alta (pode até ser um aplicativo de negócios bem grande, mas, nada muito extremo como um ERP completo) e se você tem certeza que o aplicativo deverá rodar somente no Windows, vá de Windows Forms. Desenvolver com WPF nesse caso resultaria em uma aplicação que tem a mesma “cara“, só que com um código potencialmente mais complexo.
Aplicativos utilitários: pequenos aplicativos utilitários são mais facilmente desenvolvidos com Windows Forms.
Equipe com vasta experiência em Windows Forms e pouca/nenhuma experiência em WPF: se a sua equipe já tem uma vasta experiência com Windows Forms e pouca ou nenhuma experiência com WPF, não perca tempo durante o projeto para que a equipe aprenda WPF. Na minha opinião, o decorrer de um projeto não é hora de ficar aprendendo tecnologia do zero. Se você tem a intenção de utilizar WPF em um projeto no futuro, treine a sua equipe com antecedência, porque os conceitos não são aprendidos da noite para o dia.
Quando utilizar WPF?
Aplicações que tenham a chance de ter que rodar em várias plataformas: se você está desenvolvendo um aplicativo que deverá rodar em múltiplas plataformas, vá de WPF (mas utilize ele do “jeito certo“!). Isso te forçará a separar corretamente os códigos de negócio e design, facilitando o compartilhamento de código entre as diferentes plataformas. Além disso, se você pretende desenvolver aplicativos móveis com as ferramentas da Xamarin, muito provavelmente você conseguirá compartilhar uma boa parte das ViewModels entre as plataformas (sem falar do conhecimento de XAML que será útil se você escolher trabalhar com ele nas ferramentas da Xamarin).
Aplicações gigantescas ou de complexidade extrema: se a sua aplicação é gigantesca ou tem uma complexidade extremamente grande, a utilização correta do WPF fará com que você separe bem o código e tenha um projeto mais estruturado. A manutenção desses projetos ficará menos complexa caso você utilize o WPF com a arquitetura MVVM.
Aplicações com interfaces ricas: a sua aplicação precisa ter interfaces ricas, com transformações geométricas, 3D ou manipulação de mídia? Então não tem nem o que pensar. O WPF traz essas possibilidades nativamente, além de conseguir utilizar todo o poder da sua placa gráfica (coisa que não acontece com o Windows Forms).
Atenção! Evite isso:
Utilizar o Windows Forms ou WPF sem bibliotecas de terceiros: tanto os controles nativos do Windows Forms como do WPF são muito básicos e não são bons o suficiente para desenvolver um aplicativo comercial de qualidade. É importante investir um pouco de grana na compra de uma suíte de componentes. Dessa forma você acabará economizando tempo, uma vez que você não terá que ficar “reinventando a roda” toda vez que precisar de uma funcionalidade inexistente nos controles nativos.
Escolher o WPF “só por escolher”: não escolha o WPF só porque ele é mais novo ou mais “elegante“. Os conceitos por traz do WPF são bem mais complexos que o Windows Forms e, como mencionei anteriormente, a curva de aprendizado é maior. Analise com cuidado o seu projeto para ver se o WPF realmente é a melhor escolha ou se você só está escolhendo ele porque “ouviu falar” que ele é melhor.
Programar aplicações WPF como você está acostumado a programar Windows Forms: se você escolher o WPF para a sua aplicação, não trabalhe com ele como se você estivesse programando Windows Forms! Não comece a arrastar controles e utilizar os seus eventos como você está potencialmente acostumado com o Windows Forms. Utilize uma arquitetura robusta (como o MVVM), tire proveito dos bindings, comandos, templates e tudo mais que o WPF traz de bom para você. Se você trabalhar com o WPF da mesma forma que está acostumado com o Windows Forms, o seu projeto ficará parecendo um Frankenstein e, o pior de tudo, a aparência da aplicação ficará igualzinha a uma aplicação Windows Forms. Ou seja, você terá uma aplicação idêntica com um código mais complexo. Não é uma boa ideia.
Programar aplicações Windows Forms sem separar o código de negócios da camada de apresentação: infelizmente, esse é um erro que ainda acontece muito frequentemente. O programador não separa a lógica de negócios e implementa tudo por trás do código do formulário. Se você ainda comete esse erro, pare agora mesmo! Sério. Esse é o pior erro que você pode cometer. Se você continuar persistindo nesse erro, quando a sua aplicação começar a ficar muito grande, ninguém vai conseguir dar manutenção nela (nem mesmo você)!
Ignorar o WPF ou falar mal dele sem conhecer: muita gente que já tem bastante experiência com Windows Forms e começa a estudar WPF acaba desistindo no meio (por causa da complexidade) e passa a falar mal do WPF. Isso é um tremendo erro. Para conseguir identificar qual é a melhor plataforma para cada tipo de aplicativo, você precisa conhecer muito bem as duas plataformas. Dessa forma, não ignore o WPF. Aprenda, treine, ponha em prática em um aplicativo de teste. Só assim você conseguirá tirar proveito de ambas as plataformas, independentemente de qual você escolher para cada projeto.
Concluindo
Tanto o Windows Forms quanto o WPF são plataformas de desenvolvimento de aplicações desktop que já estão “prontas“, ou seja, ambas estão estáveis e completas. Existe uma grande sobreposição de funcionalidades que estão presentes nas duas plataformas. Por isso, é importante conhecer ambas as plataformas para conseguir decidir qual delas utilizar em cada tipo de projeto.
Neste artigo você conheceu as principais características das duas plataformas, bem como a minha opinião sobre quando utilizar o Windows Forms e quando utilizar o WPF. E você? Concorda comigo ou tem uma opinião diferente? Deixe as suas observações 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.
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:
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:
' 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“:
' 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:
' 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:
' 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“:
' 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.
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:
' 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:
' 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.
Ao responder questões que recebo por e-mail ou quando tiro um tempinho para dar uma olhada nos fóruns da MSDN, tenho percebido ultimamente que muitos programadores .NET ainda não conhecem arquivos de recursos! Essa é uma funcionalidade tão útil quando estamos desenvolvendo aplicativos com o C# ou VB.NET e eu acho uma pena que tanta gente ainda não conheça ou não saiba utilizar. Pensando nessa deficiência, resolvi escrever este artigo detalhando como utilizar arquivos de recursos no C# e VB.NET.
O que são arquivos de recursos?
Primeiramente, antes de mostrar como podemos utiliza-los, vamos entender o que são os arquivos de recursos. Eles nada mais são do que arquivos que apontam para recursos (como strings, imagens, ícones, arquivos, etc.) que poderão ser utilizados na nossa aplicação. Esses arquivos têm a extensão “resx” e, na verdade, são um atalho para os arquivos físicos que ficam normalmente armazenados na pasta “Resources” do nosso projeto.
Onde ficam os arquivos de recursos?
Quando criamos um projeto desktop (Windows Forms ou WPF) com o C# ou VB.NET, o próprio Visual Studio já cria automaticamente um arquivo de recursos que podemos utilizar no projeto todo. No C#, esse arquivo fica armazenado dentro da seção “Properties” do “Solution Explorer“:
Já no VB.NET, o arquivo de recursos que é criado automaticamente pelo Visual Studio pode ser acessado na tela de propriedades do projeto:
Tipos de recursos suportados
Clicando no botão para escolha do tipo de recurso, percebemos que podemos adicionar praticamente qualquer coisa dentro do arquivo de recursos:
As opções vão desde strings, imagens e ícones até arquivos de mídia (sons e vídeos) ou qualquer outro tipo de arquivo (como PDFs, DOCs, etc). Tudo pode ser armazenado dentro do arquivo de recursos, caso necessário.
Adicionando novos recursos
Para adicionarmos novos recursos, basta clicarmos no botão “Add Resource” e escolher o tipo de recurso que queremos adicionar:
Na maior parte do tempo, vamos trabalhar principalmente com duas opções: nova string ou arquivo existente. Ao adicionarmos um arquivo existente, ele automaticamente será adicionado na categoria pertinente (quando adicionamos um arquivo “.bmp” pré-existente, ele será automaticamente adicionado na categoria “imagens“).
Além dessas opções, o Visual Studio também conta com um simples editor de imagens, ícones e arquivos de texto para criarmos novos recursos diretamente pelo Visual Studio (ao invés de selecionarmos um arquivo pré-existente).
Agora, imagine que temos uma string longa que será utilizada várias vezes no nosso sistema em diferentes lugares. Ao invés de digitarmos esse texto várias vezes diretamente nos controles, podemos adicionar essa string como um recurso que posteriormente poderá ser utilizado quantas vezes forem necessárias. Para adicionarmos uma nova string no arquivo de recursos, basta irmos até a seção de strings do arquivo de recursos e adicionarmos uma nova entrada na lista:
O código para acessarmos esse recurso através do código é um pouco diferente quando comparamos o C# com o VB.NET. No C#, acessamos o conteúdo do arquivo de recursos através do seu namespace (no caso do arquivo de recursos padrão, ele fica dentro de “Properties.Resources“). Já no VB.NET, acessamos os recursos através da propriedade “My.Resources“.
Para conferirmos essa utilização, vamos adicionar uma Label no nosso formulário e, no evento “Load” do formulário, vamos configurar o seu texto para a string que acabamos de criar:
' VB.NET
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.Text = My.Resources.MinhaString
End Sub
Execute a aplicação e veja o resultado:
Trabalhando com imagens no arquivo de recursos
Da mesma forma que podemos adicionar strings nos arquivos de recursos, o processo para adicionarmos imagens também é muito fácil. Primeiramente, vamos escolher uma imagem para testarmos. Eu escolhi essa aqui no Pixabay (estou na “vibe” de bebês porque meu segundo filho nasceu há dois meses). Se quiser baixar diretamente aqui do meu site, clique aqui.
Uma vez baixado o arquivo para o seu computador, escolha a opção para adicionar um novo recurso através de um arquivo pré-existente e selecione a imagem que acabamos de baixar. Note que, após a inclusão do recurso, podemos alterar o nome do recurso:
Para facilitar a nossa vida, vamos renomear esse recurso, dando o nome de “Baby” para ele. Feito isso, vamos adicionar um PictureBox no nosso formulário e vamos clicar na opção “Choose Image“:
Agora, note que, dentro da tela “Choose Resource“, nós conseguimos encontrar a imagem que acabamos de adicionar no arquivo de recursos:
Caso quiséssemos, nós poderíamos acessar essa imagem via código também:
E outros tipos de arquivos, como PDF?
Como mencionei anteriormente, podemos adicionar qualquer tipo de arquivo dentro dos nossos arquivos de recursos. Quando o arquivo que está sendo adicionado não se enquadra em nenhuma das categorias pré-estabelecidas, ele simplesmente será adicionado como um “arquivo“. Por exemplo, se quisermos adicionar esse PDF no arquivo de recursos, poderemos acessá-lo como um array de bytes via código:
Nós não precisamos ficar limitados com o arquivo de recursos “padrão” que é criado automaticamente pelo Visual Studio. Através da opção “Add New Item” do nosso projeto, podemos adicionar outros arquivos de recursos (ou até mesmo em um outro projeto, como em uma “Class Library“):
Em seguida, para utilizarmos os recursos armazenados dentro desse outro arquivo, basta utilizarmos o nome completo do recurso (no C#) ou a propriedade “My.Resources” seguida do nome do arquivo de recursos (no VB.NET):
Atenção! Os recursos aumentam o tamanho do executável!
Uma coisa que devemos ficar atentos é que todos os arquivos adicionados nos arquivos de recursos do nosso projeto serão compilados juntamente com a aplicação (ou com a biblioteca, se os arquivos de recursos estiverem armazenados dentro de uma dll). Dessa forma, o tamanho do executável ou dll aumentará significativamente dependendo do tamanho dos itens que adicionarmos nos arquivos de recursos. Veja só o tamanho dessa simples aplicação de exemplo que criamos neste artigo:
E como funciona no WPF? E no MVC?
Tudo o que vimos neste artigo sobre arquivos de recursos não vale somente para o Windows Forms. O WPF se comporta exatamente da mesma maneira. E existem artigos mostrando como utilizar arquivos de recursos no Web Forms e no MVC.
Concluindo
Os arquivos de recursos, apesar de serem uma funcionalidade básica de aplicações desenvolvidas com o .NET Framework, é muitas vezes desconhecido pelos programadores desse tipo de aplicação. Eles ajudam muito na organização dos nossos projetos e é uma funcionalidade que todo programador deveria pelo menos conhecer.
Neste artigo você conferiu como utilizar o arquivo de recursos “padrão” que é criado automaticamente pelo Visual Studio nos projetos Windows Forms e WPF. Além disso, você conferiu também como adicionar outros arquivos de recursos no C# e VB.NET, além do que é criado automaticamente pelo Visual Studio. Por fim, você viu que devemos ficar atentos com o tamanho da aplicação, que terá um crescimento diretamente relacionado com o tamanho dos itens que adicionarmos nos arquivos de recursos.
E você? Utiliza arquivos de recursos nos seus projetos? Qual a sua opinião sobre eles? Aprova ou não aprova? Deixe as suas observaçõ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.
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.
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:
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.
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:
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.