20 07 2016
Utilizando o Crystal Reports com MVVM no WPF
Sempre que desenvolvemos um projeto de software minimamente complexo, é recomendado que pensemos com muito cuidado na sua arquitetura antes mesmo de começar a codificar. Essa recomendação é ainda maior quando trabalhamos com WPF, devido à sua poderosíssima estrutura de data-binding que, se não utilizada, faz com que perca todo o sentido a utilização do WPF na construção do projeto.
Sem sombra de dúvidas, a arquitetura mais utilizada em projetos WPF é o MVVM. Quando utilizamos esse tipo de arquitetura, toda a lógica para alimentar as janelas da nossa aplicação fica armazenada nas ViewModels. Dessa forma, faz todo sentido que o carregamento do relatório fique separado na ViewModel. Isso é justamente o que eu vou mostrar neste artigo: como utilizar o Crystal Reports com MVVM no WPF.
Criando o projeto de exemplo
Antes de tudo, vamos começar criando um projeto do tipo “WPF Application“. Nesse projeto, vamos adicionar um relatório do Crystal Reports que será extremamente simples, contendo somente uma caixa de texto. Vamos dar o nome de “RelatorioTeste” para esse relatório:
Agora, vamos adicionar uma nova classe neste projeto, que servirá como ViewModel para a nossa janela. Dê o nome de “MainViewModel” para essa nova classe. O conteúdo dela também será muito simples: teremos uma propriedade do tipo “ReportDocument” e, no construtor da classe, setaremos essa propriedade utilizando uma nova instância do nosso “RelatorioTeste“:
// C# public class MainViewModel { public CrystalDecisions.CrystalReports.Engine.ReportDocument Report { get; set; } public MainViewModel() { Report = new RelatorioTeste(); } }
' VB.NET Public Class MainViewModel Public Property Report As CrystalDecisions.CrystalReports.Engine.ReportDocument Public Sub New() Report = New RelatorioTeste() End Sub End Class
Em seguida, temos que criar uma instância dessa classe dentro dos static resources da nossa aplicação, de forma que consigamos utilizar essa ViewModel diretamente via XAML na nossa janela. Para isso, temos que abrir o arquivo App.xaml (ou Application.xaml), adicionamos a declaração do namespace “local” apontando para o namespace geral do nosso projeto e incluímos a linha dentro da tag “Application.Resources” declarando uma instância da MainViewModel:
<Application x:Class="CrystalWPFMVVM.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CrystalWPFMVVM" StartupUri="MainWindow.xaml"> <Application.Resources> <local:MainViewModel x:Key="MainViewModel" /> </Application.Resources> </Application>
Atenção: se a sua ViewModel estiver localizada em outro namespace (ou até mesmo em um outro assembly), você deve declarar o namespace na Application e utilizá-lo na hora de declarar a instância da ViewModel. No caso do exemplo deste artigo, a ViewModel está na raiz do mesmo projeto, portanto, declaramos e utilizamos o namespace “local“.
Por fim, vamos agora até a nossa janela, onde temos que setar o DataContext apontando para a instância de “MainViewModel” que acabamos de declarar na Application. Além disso, vamos arrastar um controle do Crystal Reports para dentro do grid e vamos dar o nome de “CrystalReportsViewer” para ele:
<Window xmlns:Viewer="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer" x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" DataContext="{StaticResource MainViewModel}"> <Grid> <Viewer:CrystalReportsViewer x:Name="CrystalReportsViewer"/> </Grid> </Window>
Nota: se você não está encontrando o controle do Crystal Reports na caixa de ferramentas do seu projeto WPF, é porque você tem que adicioná-lo manualmente. Confira a explicação detalhada desse processo no artigo: Trabalhando com o Crystal Reports no WPF.
Setando o ReportSource no code-behind da janela
Agora que já temos o DataContext da nossa janela apontando para uma instância de “MainViewModel“, como é que podemos configurar o ReportSource do controle do Crystal Reports utilizando a propriedade “Report” da nossa ViewModel? A primeira opção é configurarmos através do code-behind. Para isso, vamos até o code-behind e setamos a propriedade fazendo um cast de DataContext para MainViewModel para termos acesso à propriedade “Report“:
// C# public MainWindow() { InitializeComponent(); CrystalReportsViewer.ViewerCore.ReportSource = ((MainViewModel)DataContext).Report; }
' VB.NET Class MainWindow Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. CrystalReportsViewer.ViewerCore.ReportSource = DirectCast(DataContext, MainViewModel).Report End Sub End Class
Execute o projeto e veja que o relatório será carregado com sucesso:
Nota: se você receber uma “FileNotFoundException” vindo do controle do Crystal Reports ao executar o projeto, isso quer dizer que falta uma configuração no seu arquivo app.config. Eu mostrei como fazer essa configuração no artigo: Trabalhando com o Crystal Reports no WPF.
Setando o ReportSource diretamente no XAML
OK, configurar o ReportSource no code-behind funciona, mas, você sabe muito bem que a maioria dos programadores que trabalham com WPF é aficionado por fazer tudo direto no XAML. Tem como fazer isso nesse caso? Tem! Só precisamos criar uma dependency property. Eu encontrei essa alternativa em uma thread do fórum do Crystal Reports no site da SAP: Binding Report Source on WPF Crystal Report Viewer Solution.
Basicamente, temos que adicionar uma nova classe estática dentro do nosso projeto, dando o nome de “ReportSourceBehaviour“. Dentro dessa classe, declaramos a dependency property e a sua implementação:
// C# public static class ReportSourceBehaviour { public static readonly System.Windows.DependencyProperty ReportSourceProperty = System.Windows.DependencyProperty.RegisterAttached( "ReportSource", typeof(object), typeof(ReportSourceBehaviour), new System.Windows.PropertyMetadata(ReportSourceChanged)); private static void ReportSourceChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e) { var crviewer = d as SAPBusinessObjects.WPF.Viewer.CrystalReportsViewer; if (crviewer != null) { crviewer.ViewerCore.ReportSource = e.NewValue; } } public static void SetReportSource(System.Windows.DependencyObject target, object value) { target.SetValue(ReportSourceProperty, value); } public static object GetReportSource(System.Windows.DependencyObject target) { return target.GetValue(ReportSourceProperty); } }
' VB.NET Public NotInheritable Class ReportSourceBehaviour Public Shared ReadOnly ReportSourceProperty As System.Windows.DependencyProperty = _ System.Windows.DependencyProperty.RegisterAttached("ReportSource", GetType(Object), GetType(ReportSourceBehaviour), New System.Windows.PropertyMetadata(AddressOf ReportSourceChanged)) Private Shared Sub ReportSourceChanged(d As System.Windows.DependencyObject, e As System.Windows.DependencyPropertyChangedEventArgs) Dim crviewer = TryCast(d, SAPBusinessObjects.WPF.Viewer.CrystalReportsViewer) If crviewer IsNot Nothing Then crviewer.ViewerCore.ReportSource = e.NewValue End If End Sub Public Shared Sub SetReportSource(target As System.Windows.DependencyObject, value As Object) target.SetValue(ReportSourceProperty, value) End Sub Public Shared Function GetReportSource(target As System.Windows.DependencyObject) As Object Return target.GetValue(ReportSourceProperty) End Function End Class
Feito isso, podemos voltar ao nosso XAML e fazemos o binding direto utilizando a dependency property:
<Window xmlns:Viewer="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer" x:Class="CrystalWPFMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CrystalWPFMVVM" Title="MainWindow" Height="350" Width="525" DataContext="{StaticResource MainViewModel}"> <Grid> <Viewer:CrystalReportsViewer x:Name="CrystalReportsViewer" local:ReportSourceBehaviour.ReportSource="{Binding Path=DataContext.Report, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=FrameworkElement}}"/> </Grid> </Window>
Nota: não esqueça de declarar o namespace “local” apontando para o namespace raiz do projeto. Caso você tenha criado a classe “ReportSourceBehaviour” em algum outro lugar do projeto (ou até mesmo em um assembly diferente), você precisa declarar o namespace na janela e utilizá-lo (ao invés de utilizar o namespace “local“).
Concluindo
Utilizar o Crystal Reports com MVVM no WPF não só é possível como é muito simples. Neste artigo você conferiu como declarar o relatório na ViewModel e como utilizar o relatório declarado na ViewModel dentro das janelas da aplicação. Isso pode ser feito tanto via code-behind como diretamente no XAML.
E você, trabalha com o Crystal Reports no WPF? Utiliza a arquitetura MVVM? Se você utiliza, como é que você faz o carregamento dos relatórios? Do mesmo jeito que eu mostrei aqui no artigo? Se você não utiliza, qual é o motivo para ter escolhido trabalhar sem o MVVM nesse caso? De qualquer forma, deixe as suas observações na caixa de comentários logo abaixo.
Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Juntando dois relatórios do Report Viewer em um só Windows Forms ou WPF? Qual utilizar?