André Alves de Lima

Talking about Software Development and more…

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

Newsletter do André Lima

* indicates required



Powered by MailChimp

Deixe uma resposta

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