Using the WPF Report Viewer in an MVVM Application

As of 2016 v3 release, FlexReport for WPF is now available in beta! Let's look at the basics of MVVM and how Flexreport can be integrated into your MVVM app. The Model View ViewModel is a Presentation Model Design pattern that separates UI of an application from data or business logic and yet providing communication between the UI and business logic. Following is a brief description of the 3 parts of MVVM application: Data Model: Model is the actual data or domain that we are dealing with. It is completely separated from UI of the application. It basically holds the information of the project, but is not responsible for fetching data from server. ViewView: This is the UI of the application, the actual formatting to make the application look pretty is done here. The job of the View is to present the data to the user. ViewModelViewModel: View Model is responsible for bringing the data from Model to the UI, or taking input from UI and placing it in Model.

Using Report Viewer in WPF application with MVVM design pattern: Use Case

Suppose your app design calls for a list of reports on the left pane, a report viewer on the right, and a toolbar to control the report viewer functionalities. MVVM In a traditional WinForms model, you'd design a form with all of the UI elements, and do all the binding in the code-behind. So your logic to bind the left pane with list of reports, bind reports with the report template, and the logic for toolbar functionalities all goes into code-behind of the same Form class where you're also adding UI Elements for View. Using this approach has following disadvantages:

  1. There's dependency between UI elements and data binding logic, as both are present in the same class
  2. The size of your view or form class increases
  3. If one developer is working on the view and another on the business logic, they can't work simultaneously on the same class

To solve this problem, it is better to separate your UI from the business logic using the MVVM pattern. Here's how you can design above problem using FlexReport in WPF application with MVVM Design:

Create Model

Your model will hold two types of data:

  1. List of Categories under which the reports need to be arranged
  2. The list of reports in the FlexReport

To accomplish the first step, you need a CategoryItem and CategoryList class:


public class CategoryItem  
{  
string _reports;  
public CategoryItem(string categoryName)  
{  
CategoryName = categoryName;  
_reports = new List<string>();  
}  
public string CategoryName  
{  
get;  
private set;  
}  

public string Reports  
{  
get { return _reports; }  
}  
public void AddReport(string reportName)  
{  
_reports.Add(reportName);  
}  

}  

To create a list of categories in UI and placing reports under it, you need a CategoryList class:


public class CategoryList : KeyedCollection string, CategoryItem, IDisposable  
{  
Stream _stream;  
public CategoryList(Stream stream)  
{  
_stream = stream;  
string[] reports = C1FlexReport.GetReportList(stream);  
using (C1FlexReport fr = new C1FlexReport())  
{  
foreach (string reportName in reports)  
{  
stream.Seek(0, SeekOrigin.Begin);  
fr.Load(stream, reportName);  
string keywords = fr.ReportInfo.Keywords;  
string[] ss = keywords.Split(new string[] { "\\r\\n" }, StringSplitOptions.None);  
for (int i = 0; i < ss.Length; i++)  
{  
var categoryName = ss[i];  
if (Contains(categoryName))  
this[categoryName].AddReport(reportName);  
else  
{  
var ci = new CategoryItem(categoryName);  
ci.AddReport(reportName);  
this.Add(ci);  
}  
}  
}  
}  
}  

public C1FlexReport GetReport(string reportName)  
{  
if (_stream != null)  
{  
_stream.Seek(0, SeekOrigin.Begin);  
var fr = new C1FlexReport();  
fr.Load(_stream, reportName);  
return fr;  
}  
return null;  
}  

protected override string GetKeyForItem(CategoryItem item)  
{  
return item.CategoryName;  
}  

public void Dispose()  
{  
_stream.Close();  
_stream = null;  
}  

}  

To send the category list to the UI, you need a Service that gets the category list made from the FlexReport .flxr file that contains all the reports. Your IReportService class looks like this:


public interface IReportService  
{  
void GetCategoryList(Action<CategoryList, Exception> callback);  
}  

And the ReportService class sends the Resource stream from .flxr file to CategoryList class to create the Category List.


public class ReportService : IReportService  
{  
public void GetCategoryList(Action<CategoryList, Exception> callback)  
{  
var asm = Assembly.GetExecutingAssembly();  
var stream = asm.GetManifestResourceStream("CommonTasksMVVM.Resources.FlexCommonTasks_WPF.flxr");  
CategoryList list = null;  
Exception ex = null;  
try  
{  
list = new CategoryList(stream);  
}  
catch (Exception ex1)  
{  
ex = ex1;  
}  
callback(list, ex);  

}  
}  

Creating ViewModel

Next, your ViewModel should define methods that can get the category list for reports, report names, and reports from the Model, so that they can be displayed and bound directly in UI XAML.


public class MainViewModel : ViewModelBase  
{  
C1FlexReport _flexReport;  
CategoryList _categoryList;  
string _errorMessage;  
// Initializes a new instance of the MainViewModel class.  
public MainViewModel(IReportService reportService)  
{  
reportService.GetCategoryList(  
(list, error) =>;  
{  
if (error != null)  
{  
ErrorMessage = error.Message;  
return;  
}  
CategoryList = list;  
FlexReport = list.GetReport(list[0].Reports[0]);  
});  
ClickReportCommand = new RelayCommand<string>(ClickReportMethod);  
Messenger.Default.Register<CleanupMsg>(this, (cm) => Cleanup());  
}  
public string ErrorMessage  
{  

get  
{  
return _errorMessage;  
}  
private set  
{  
Set(() => ErrorMessage, ref _errorMessage, value);  
}  
}  
public CategoryList CategoryList  
{  
get  
{  
return _categoryList;  
}  
private set  
{  
Set(() => CategoryList, ref _categoryList, value);  
}  

}  
public ICommand ClickReportCommand { get; private set; }  

public void ClickReportMethod(string reportName)  
{  
if (_categoryList != null)  
{  
var oldRpt = _flexReport;  
FlexReport = _categoryList.GetReport(reportName);  
if (oldRpt != null)  
{  
oldRpt.Dispose();  
}  
}  
}  
public C1FlexReport FlexReport  
{  
get  
{  
return _flexReport;  
}  
private set  
{  
Set(() =>; FlexReport, ref _flexReport, value);  
}  
}  
public override void Cleanup()  
{  
var rpt = _flexReport;  
if (rpt != null)  
{  
FlexReport = null;  
rpt.Dispose();  
}  
var list = _categoryList;  
if (list != null)  
{  
CategoryList = null;  
list.Dispose();  
}  
base.Cleanup();  
}  
public class CleanupMsg : MessageBase  
{  
}  
}  

Creating View

After defining Model and ViewModel, there is very little part left for the view. You just have to place the UI elements:

  1. An Expander control for listing category items and report names
  2. A FlexViewerPane control for displaying report
  3. A toolbar for operations on the FlexViewerPane

Since ViewModel has defined code to get data from the Model for the category list and yeport, you can directly bind the Items control with CategoryList; the Expander control with Category Name; and FlexViewerPane with FlexReport. Your XAML would look like this:



<Window x:Class="CommonTasksMVVM.MainWindow"  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
xmlns:c1="http://schemas.componentone.com/winfx/2006/xaml"  
xmlns:local="clr-namespace:CommonTasksMVVM"  
mc:Ignorable="d"  
WindowState="Maximized"  
Title="MainWindow"  
Height="350" Width="525"  
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"  
Loaded="Window_Loaded"  
>  
<Grid>  
<Grid.RowDefinitions>  
<RowDefinition Height="Auto"/>  
<RowDefinition/>  
</Grid.RowDefinitions>  
<Grid.ColumnDefinitions>  
<ColumnDefinition Width="Auto"/>  
<ColumnDefinition/>  
</Grid.ColumnDefinitions>  

<ScrollViewer Grid.RowSpan="2">  
<ItemsControl ItemsSource="{Binding CategoryList}">  
<ItemsControl.ItemTemplate>  
<DataTemplate>  
<c1:C1Expander Header="{Binding CategoryName}" IsExpanded="True" Margin="3" BorderThickness="1">  
<ItemsControl ItemsSource="{Binding Reports}" Margin="10, 0, 0, 0">  
<ItemsControl.ItemTemplate>  
<DataTemplate>  
<TextBlock Margin="0, 2, 0, 2">  
<Hyperlink  
Command="{Binding DataContext.ClickReportCommand, RelativeSource={RelativeSource AncestorType=Window}}"  
CommandParameter="{Binding}"  
>  
<TextBlock Text="{Binding}"/>  
</Hyperlink>  
</TextBlock>  
</DataTemplate>  
</ItemsControl.ItemTemplate>  
</ItemsControl>  
</c1:C1Expander>  
</DataTemplate>  
</ItemsControl.ItemTemplate>  
</ItemsControl>  
</ScrollViewer>  

<local:FVToolBar x:Name="fvToolBar" Grid.Column="1" />  

<c1:C1FlexViewerPane  
x:Name="fvp"  
Grid.Column="1"  
Grid.Row="1"  
DocumentSource="{Binding FlexReport}"  
>  
<c1:C1FlexViewerPane.ContextMenu>  
<ContextMenu x:Name="fvpContextMenu">  
<MenuItem x:Name="copyMenuItem" Header="Copy"/>  
<Separator />  
<MenuItem x:Name="selectAllMenuItem" Header="Select All"/>  
<MenuItem x:Name="deselectMenuItem" Header="Deselect"/>  
</ContextMenu>  
</c1:C1FlexViewerPane.ContextMenu>  
</c1:C1FlexViewerPane>  
</Grid>  
</Window>  




Read more about FlexReport

GrapeCity

GrapeCity Developer Tools
comments powered by Disqus