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: 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.
View: 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.
ViewModel: View Model is responsible for bringing the data from Model to the UI, or taking input from UI and placing it in Model.
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. 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:
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:
Your model will hold two types of data:
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);
}
}
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
{
}
}
After defining Model and ViewModel, there is very little part left for the view. You just have to place the UI elements:
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>