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:
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With