Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReportViewer in MVVM WPF

I'm building an MVVM Light WPF app using Visual Studio 2015. The app needs to display some SQL Server Reporting Services reports locally.

The following two solutions exist:

  • Using MS ReportViewer in WPF
  • Walkthrough: Using ReportViewer in a WPF Application

Though the first is MVVM, it's mixing UI with the view model. The second is pure code-behind.

Here's what the first example suggests:

WindowsFormsHost windowsFormsHost = new WindowsFormsHost();
reportViewer = new ReportViewer();
windowsFormsHost.Child = reportViewer;
this.Viewer = windowsFormsHost

Note that ReportViewer is a UI control. The second solution uses a code-behind file:

private void ReportViewer_Load(object sender, EventArgs e)
{
   //...
}

Is there a way to embed a local SSRS report into a WPF app and follow good MVVM practices? Thank you.

Update: No need to be fanatical! If some code-behind is needed, I'm okay with it.

like image 923
Alex Avatar asked Jun 17 '16 15:06

Alex


1 Answers

We use a view to select the report from a ComboBox and a button to run it. In the viewmodel, we have the reports' ComboBox bound to an ObservableCollection of report names and IDs. We then employ the MVVM Light Toolkit's Messaging class to send/receive "messages." Note that the base viewmodel, MyAppViewModelBase, inherits from Light Toolkit's ViewModelBase, which has the RaisePropertyChanged() defined.

Also note that we could pass the selected report's VM instead of the view's VM; that would be more efficient but will require modifications to this code. Then we'd use a base class for all the report VMs and a pattern-matching switch in the code-behind to select which report to run.

Here's the pertinent code for the viewmodel:

using GalaSoft.MvvmLight.Messaging;

public class ReportsViewModel : MyAppViewModelBase
{
    public ReportsViewModel()
    {
        // Register a listener that receives the enum of the 
        // report that's ready. The message it receives has 
        // name "SsrsReportReady" with handler SsrsReportReady.
        Messenger.Default.Register<Constants.Report>(this, "SsrsReportReady", SsrsReportReady);

        // Other logic...
    }

    // Bound to a button to run the selected report
    public ICommand RunReportRelayCommand =>
                new RelayCommand(RunReport);

    // Backing field for the selected report.
    private ReportViewModel _selectedReportVm;

    public ReportViewModel SelectedReportVm
    {
        get { return _selectedReportVm; }
        set
        {
            if (Equals(value, _selectedReportVm)) return;
            _selectedReportVm = value;

            // Built-in method from Light Toolkit to 
            // handle INotifyPropertyChanged
            RaisePropertyChanged();  
        }
    }

    private void RunReport()
    {
        // Send a message called "RunSSRSReport" with this VM attached as its data.
        Messenger.Default.Send(this, "RunSSRSReport");
    }

    // Handler for report-ready
    private void SsrsReportReady(Constants.Report obj)
    {
        ShowReport = true;
        IsRunReportButtonEnabled = true;
        RunReportButtonContent = Constants.BtnGenerateReport;

        // View uses Material Design's Expander control.
        // We expand/collapse sections of the view.
        ExpandReport = true;
        ExpandParameters = false;
    }
}

In the code-behind of the view:

using GalaSoft.MvvmLight.Messaging;

public partial class ReportsView : UserControl
{
    public ReportsView()
    {
        InitializeComponent();

        // Register a listener for the "RunSSRSReport" 
        // message, called from our viewmodel. Its 
        // handler is RunSsrsReport and its data type 
        // is ReportsViewModel.
        Messenger.Default.Register<ReportsViewModel>(this, "RunSSRSReport", RunSsrsReport);

        DataContext = new ReportsViewModel();
    }

    // Handler to run the selected report. 
    private void RunSsrsReport(ReportsViewModel obj)
    {
        // Basic validation
        if (obj.SelectedReportVm == null || obj.SelectedReportVm.Id.Equals(-1)) 
        {
            return;
        }

        // Ugly switch to run the correct report.
        // It can be re-written with pattern matching.
        switch (obj.SelectedReportVm.Id)
        {
            case (int)Constants.Report.ReportA:
                RunReportA(obj);
                break;
            case (int)Constants.Report.ReportB:
                RunReportB(obj);
                break;
            // other reports....
        }
    }   

    // Run the report using dataset and tableadapter.
    // Modify to use your code for running the report.
    private void RunReportA(ReportsViewModel reportsViewModel)
    {
        var dataSet = new ReportADataSet();
        dataSet.BeginInit();

        // We reference the ReportViewer control in XAML.
        ReportViewer.ProcessingMode = ProcessingMode.Local;
        ReportViewer.LocalReport.ShowDetailedSubreportMessages = true;
        ReportViewer.LocalReport.DataSources.Clear();
        var dataSource = new ReportDataSource
        {
            Name = "ReportA_DS",
            Value = dataSet.uspReportA  // Uses a stored proc
        };
        ReportViewer.LocalReport.DataSources.Add(dataSource);
        ReportViewer.LocalReport.ReportEmbeddedResource =
            "MyApp.Reports.ReportA.rdlc";
        dataSet.EndInit();
        new reportATableAdapter { ClearBeforeFill = true }
            .Fill(dataSet.uspReportA);

        // Send message back to viewmodel that the report is ready.
        Messenger.Default.Send(Constants.Report.ReportA, "SsrsReportReady");
    }
}

The report view has a WindowsFormsHost with name ReportViewer, referenced in above code-behind:

<WindowsFormsHost Width="Auto" Height="500">                   
    <rv:ReportViewer x:Name="ReportViewer" />
</WindowsFormsHost>
like image 128
Alex Avatar answered Oct 18 '22 02:10

Alex