I spent an hour or two trying to find a solution to this problem so I thought I would post it here for others.
The problem:
You wish to bind the report source of your viewer to a property, but you find that the property is not available directly on the viewer. You are following the MVVM pattern and you do not wish to add the code directly behind the view as you wish to set the report source from the view model.
This is how I solved it - if anyone has any comments or suggestions I would appreciate it.
Create a static class to hold an attached property we will use on our viewer:
public static class ReportSourceBehaviour { public static readonly DependencyProperty ReportSourceProperty = DependencyProperty.RegisterAttached( "ReportSource", typeof(object), typeof(ReportSourceBehaviour), new PropertyMetadata(ReportSourceChanged) ); private static void ReportSourceChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { var crviewer = d as CrystalReportsViewer; if (crviewer != null) { crviewer.ViewerCore.ReportSource = e.NewValue; } } public static void SetReportSource(DependencyObject target, object value) { target.SetValue(ReportSourceProperty, value); } public static object GetReportSource(DependencyObject target) { return target.GetValue(ReportSourceProperty); } }
Note the ReportSourceChanged code in particular, this is where we set the report source on the viewer.
Then we need to add the binding on the viewer. This stumped me for a while as I could not work out why the the datacontext of the viewer would not pick up the viewmodel (the datacontext of the viewer seems to reference itself rather than pickup the datacontext of its parent). The simplest answer I found was to bind to the parent datacontext.
First setup the namespaces if you haven't already (adjust the namespace to suit):
xmlns:sap="clr-namespace:SAPBusinessObjects.WPF.Viewer;assembly=SAPBusinessObjects.WPF.Viewer" xmlns:local="clr-namespace:[NamespaceWhereReportSourceBehaviourResides]"
Then add in the viewer:
<sap:CrystalReportsViewer local:ReportSourceBehaviour.ReportSource="{Binding Path=DataContext.ReportSource, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=FrameworkElement}}" />
Note the binding path, this needs to point to the property on your viewmodel that holds the reportsource (in my case the property is just called reportsource).
Hope this helps someone out there.