WPF Report Engine, Part 2

In the previous paper, I described the printing process of the ListView. In this paper, I will expand the previous implementation to support grouping of data.

Part 1
Part 2
Part 3
Part 4

The Story So Far

In the previous paper, I showed the process of printing a ListView using the WPF printing features. Unlike the other approaches that used FlowDocuments to print data, my approach used the ListView to print data. It gave us the flexibility to use all of the WPF controls and we can implement custom Views to print data. The main structure of the generated reports in the previous implementation was as follows:

In fact, the main task in the previous paper was dividing the main data source to smaller ones in such a way that each one fits one page. In this paper, the structure of the report pages will be changed as follows:

Grouping the data is one of the main requirements that any report engine should support. In order to do so, a report engine should provide the following features to report designers.

· Group Description: a group description is a criterion that specifies how a data source should be divided into groups.


·
Group header: GroupHeader is the first section of the group. Report designers can put custom content about the group here. GroupHeader does not repeat per page.


·
Group footer: GroupFooter is the last section of the group. Generally, group footers contain the aggregations of items in the group. GroupFooter does not repeat per page.


·
Group Page Header: GroupPageHeaders are similar to the GroupHeaders, but unlike them that displayed in the beginning of the group, GroupPageHeaders repeat in every page.


·
Group Page Footer: GroupPageFooters are similar to the GroupFooters, but unlike them that displayed in the end of the group, GroupPageFooters repeat in every page of the groups.


·
Accessing the data of the group: All of the footers and headers of a group should have access to the data items of the group. Accessing this data, report designers can generate custom aggregations and dynamic contents.

At first glance, I thought that I can use the grouping feature of the ListView, but unfortunately, it is not designed to support printing. Before delving into the main design, I will discuss the deficiencies of the ListView in terms of printing data.

ListView Grouping

ListView has a nice support for grouping data. It provides the developer a flexible approach to group data. The process of grouping data can be divided into two subsections.

1. Logical grouping of the data source.

2. Providing a custom view & group style to visualize the logical grouping of data.


WPF provides the CollectionViewSource and CollectionView classes that can be used to group the logical data. You can think of a collection view as a layer on top of a binding source collection that allows you to navigate and display the collection based on sort, filter, and group queries, all without having to manipulate the underlying source collection itself according to the MSDN. CollectionViewSource provides a mechanism for creating the CollectionViews in XAML. CollectionViews do not change their underling data source and as a result a data source can have multiple views. Among the properties of the CollectionView, the Groups and GroupDescriptions properties are more important for us in this paper. GroupDescriptions is a collection of GroupDescription classes. GroupDescription Provides an abstract base class for types that describe how to divide the items in a collection into groups according to MSDN. On the other hand, the Groups property contains the groups of data. Each group has been represented by a CollectionViewGroup class. Once one adds a groupDescription to the GroupDescriptions collection, the Groups property will contain the groups of data according to the provided GroupDescription. The following example explains the relation of the classes.

List<Data> list = new List<Data>();

list.Add(new Data(1, "John", 10));

list.Add(new Data(2, "John", 12));

list.Add(new Data(3, "Siyamand", 6));

list.Add(new Data(4, "Siyamand", 18));

list.Add(new Data(5, "Robbe", 22));

list.Add(new Data(6, "Robbe", 13));

ListCollectionView view = new ListCollectionView(list);


At this state, the view contains 6 items and its Groups property is null;


PropertyGroupDescription groupDescription =new PropertyGroupDescription("Name");

view.GroupDescriptions.Add(groupDescription);

After running the above code, the view.Groups will contain 3 CollectionViewGroups and each CollectionViewGroup will contain 2 items. So far, so good.

ListView provides the GroupStyle property that enables the designer to provide custom style for the groups. Here is an example of MSDN:

<Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'

xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'

>

<Window.Resources>

<XmlDataProvider x:Key="MyData" XPath="/Info">

<x:XData>

<Info xmlns="">

<Item ID="12345" Name="Book 1" Price="$32.05"

Author="Author A" Catalog="Business"/>

<Item ID="13590" Name="Book 2" Price="$10.00"

Author="Author B" Catalog="Language"/>

<Item ID="24678" Name="Book 3" Price="$9.00"

Author="Author C" Catalog="Language"/>

<Item ID="65432" Name="Book 4" Price="$8.50"

Author="Author D" Catalog="Business"/>

<Item ID="11233" Name="Book 5" Price="$19.00"

Author="Author E" Catalog="Health"/>

<Item ID="94837" Name="Book 6" Price="$8.50"

Author="Author F" Catalog="Language"/>

</Info>

</x:XData>

</XmlDataProvider>

<CollectionViewSource x:Key='src'

Source="{Binding Source={StaticResource MyData},

XPath=Item}">

<CollectionViewSource.GroupDescriptions>

<PropertyGroupDescription PropertyName="@Catalog" />

</CollectionViewSource.GroupDescriptions>

</CollectionViewSource>

</Window.Resources>

<ListView ItemsSource='{Binding Source={StaticResource src}}'

BorderThickness="0">

<ListView.GroupStyle>

<GroupStyle>

<GroupStyle.ContainerStyle>

<Style TargetType="{x:Type GroupItem}">

<Setter Property="Margin" Value="0,0,0,5"/>

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="{x:Type GroupItem}">

<Expander IsExpanded="True" BorderBrush="#FFA4B97F"

BorderThickness="0,0,0,1">

<Expander.Header>

<DockPanel>

<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"

Margin="5,0,0,0" Width="100"/>

<TextBlock FontWeight="Bold"

Text="{Binding Path=ItemCount}"/>

</DockPanel>

</Expander.Header>

<Expander.Content>

<ItemsPresenter />

</Expander.Content>

</Expander>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</GroupStyle.ContainerStyle>

</GroupStyle>

</ListView.GroupStyle>

<ListView.View>

<GridView>

<GridViewColumn Header="ID"

DisplayMemberBinding="{Binding XPath=@ID}"

Width="100" />

<GridViewColumn Header="Name"

DisplayMemberBinding="{Binding XPath=@Name}"

Width="140" />

<GridViewColumn Header="Price"

DisplayMemberBinding="{Binding XPath=@Price}"

Width="80" />

<GridViewColumn Header="Author"

DisplayMemberBinding="{Binding XPath=@Author}"

Width="80" />

</GridView>

</ListView.View>

</ListView>

</Window>

If we don’t need custom data context for the Group Headers and Footers, then the Grouping feature of the ListView would be perfect, but we need custom DataContext for the Group footers and headers. Besides this, there are only GroupPageFooters and GroupPageHeaders that repeat per page, the GroupHeader only appears in the first page of the group and the GroupFooter only appears in the last page of the group. Handling this feature in the GroupStyle is difficult. Finally, in the GroupStyle, the Group Headers and footers appear after the GridView header. In most of the applications, it is not acceptable.

Structure of Code

The following class diagram illustrates the main classes of the report engine. In the previous version, I had described the PrintableListView, DocumentPaginationExtentsion, DocumentPaginatorSource, and HeaderFooterDataContext classes. Although I have done some refactoring on these classes in the new version, but I prefer to ignore them, since the refactoring was straight forward. Instead I consider the ReportGroup class.

ReportGroup class

ReportGroup class is designed to provide the grouping functionality. It has the following properties:

public ObservableCollection<GroupDescription> GroupDescriptions

{

get

{

return (ObservableCollection<GroupDescription>)this.GetValue(GroupDescriptionsProperty);

}

set

{

this.SetValue(GroupDescriptionsProperty, value);

}

}

public static DependencyProperty GroupDescriptionsProperty =

DependencyProperty.Register("GroupDescriptions", typeof(ObservableCollection<GroupDescription>), typeof(ReportGroup), new PropertyMetadata());

public DataTemplate GroupFooter

{

get

{

return (DataTemplate)this.GetValue(GroupFooterProperty);

}

set

{

this.SetValue(GroupFooterProperty, value);

}

}

public static DependencyProperty GroupFooterProperty =

DependencyProperty.Register("GroupFooter", typeof(DataTemplate), typeof(ReportGroup), new PropertyMetadata());

public DataTemplate GroupHeader

{

get

{

return (DataTemplate)this.GetValue(GroupHeaderProperty);

}

set

{

this.SetValue(GroupHeaderProperty, value);

}

}

public static DependencyProperty GroupHeaderProperty =

DependencyProperty.Register("GroupHeader", typeof(DataTemplate), typeof(ReportGroup), new PropertyMetadata());

public DataTemplate GroupPageFooter

{

get

{

return (DataTemplate)this.GetValue(GroupPageFooterProperty);

}

set

{

this.SetValue(GroupPageFooterProperty, value);

}

}

public static DependencyProperty GroupPageFooterProperty =

DependencyProperty.Register("GroupPageFooter", typeof(DataTemplate), typeof(ReportGroup), new PropertyMetadata());

public DataTemplate GroupPageHeader

{

get

{

return (DataTemplate)this.GetValue(GroupPageHeaderProperty);

}

set

{

this.SetValue(GroupPageHeaderProperty, value);

}

}

public static DependencyProperty GroupPageHeaderProperty =

DependencyProperty.Register("GroupPageHeader", typeof(DataTemplate), typeof(ReportGroup), new PropertyMetadata());

public Size HeaderSize

{

get

{

return (Size)this.GetValue(HeaderSizeProperty);

}

set

{

this.SetValue(HeaderSizeProperty, value);

}

}

public static DependencyProperty HeaderSizeProperty =

DependencyProperty.Register("HeaderSize", typeof(Size), typeof(ReportGroup), new PropertyMetadata());

public Size FooterSize

{

get

{

return (Size)this.GetValue(FooterSizeProperty);

}

set

{

this.SetValue(FooterSizeProperty, value);

}

}

public static DependencyProperty FooterSizeProperty =

DependencyProperty.Register("FooterSize", typeof(Size), typeof(ReportGroup), new PropertyMetadata());

public Size PageHeaderSize

{

get

{

return (Size)this.GetValue(PageHeaderSizeProperty);

}

set

{

this.SetValue(PageHeaderSizeProperty, value);

}

}

public static DependencyProperty PageHeaderSizeProperty =

DependencyProperty.Register("PageHeaderSize", typeof(Size), typeof(ReportGroup), new PropertyMetadata());

public Size PageFooterSize

{

get

{

return (Size)this.GetValue(PageFooterSizeProperty);

}

set

{

this.SetValue(PageFooterSizeProperty, value);

}

}

public static DependencyProperty PageFooterSizeProperty =

DependencyProperty.Register("PageFooterSize", typeof(Size), typeof(ReportGroup), new PropertyMetadata());

internal int groupIndex;

internal int groupPage;

The Render method is the main part of the class than needs clarification. It is used by the DocumentPaginatorSource class to generate group layout & content per page. Its code is as follows:

internal FrameworkElement Render(object parentDataContext, CollectionView sourceView, ViewBase view, int itemsApproximation, ref int itemIndex, Rect size, out BindingList<object> list)

{

// The index of the grid row of the ListView

int listViewRowIndex = 0;

double pageHeight = size.Height;

// The layout of the group

Grid grid = new Grid();

grid.RowDefinitions.Add(new RowDefinition());

grid.RowDefinitions[listViewRowIndex].Height = new GridLength(1, GridUnitType.Star);

CollectionViewGroup group = sourceView.Groups[groupIndex] as CollectionViewGroup;

// Add header

if (groupPage == 0 && GroupHeader != null)

{

ContentControl groupHeaderControl = new ContentControl();

groupHeaderControl.ContentTemplate = GroupHeader;

groupHeaderControl.Height = HeaderSize.Height;

pageHeight -= HeaderSize.Height;

groupHeaderControl.Content = new HeaderFooterDataContext(parentDataContext, group.Items);

RowDefinition row = new RowDefinition();

row.Height = new GridLength(HeaderSize.Height, GridUnitType.Pixel);

grid.RowDefinitions.Insert(0, row);

grid.Children.Add(groupHeaderControl);

Grid.SetRow(groupHeaderControl, 0);

listViewRowIndex++;

}

// Page header

if (GroupPageHeader != null)

{

pageHeight -= this.PageHeaderSize.Height;

}

// Page footer

if (GroupPageFooter != null)

{

pageHeight -= this.PageFooterSize.Height;

}

list = new BindingList<object>();

ListView listview = UIUtility.createListView(group.Items, UIUtility.CreateDeepCopy<ViewBase>(view), itemsApproximation, ref itemIndex, new Rect(size.Left, size.Top, size.Width, pageHeight), out list);

HeaderFooterDataContext headerFooterContent = new HeaderFooterDataContext(parentDataContext, list);

headerFooterContent.IsFirstPage = groupPage == 0;

// group page header

if (GroupPageHeader != null)

{

ContentControl groupPageHeaderControl = new ContentControl();

groupPageHeaderControl.ContentTemplate = GroupPageHeader;

groupPageHeaderControl.Height = this.PageFooterSize.Height;

pageHeight -= this.PageHeaderSize.Height;

groupPageHeaderControl.Content = headerFooterContent;

RowDefinition row = new RowDefinition();

row.Height = new GridLength(PageHeaderSize.Height, GridUnitType.Pixel);

grid.RowDefinitions.Insert(listViewRowIndex, row);

grid.Children.Add(groupPageHeaderControl);

Grid.SetRow(groupPageHeaderControl, listViewRowIndex);

listViewRowIndex++;

}

// group page footer

if (GroupPageFooter != null)

{

ContentControl groupPageFooterControl = new ContentControl();

groupPageFooterControl.ContentTemplate = GroupPageFooter;

groupPageFooterControl.Height = this.PageFooterSize.Height;

pageHeight -= this.PageHeaderSize.Height;

groupPageFooterControl.Content = headerFooterContent;

RowDefinition row = new RowDefinition();

row.Height = new GridLength(PageFooterSize.Height, GridUnitType.Pixel);

grid.RowDefinitions.Insert(listViewRowIndex + 1, row);

grid.Children.Add(groupPageFooterControl);

Grid.SetRow(groupPageFooterControl, listViewRowIndex + 1);

}

// Group footer

if (itemIndex >= group.ItemCount - 1)

{

headerFooterContent.IsLastPage = true;

if (GroupFooter != null &&

listview.ActualHeight + FooterSize.Height <= pageHeight)

{

groupPage = 0;

groupIndex++;

itemIndex = 0;

ContentControl groupFooterControl = new ContentControl();

groupFooterControl.ContentTemplate = GroupHeader;

groupFooterControl.Height = FooterSize.Height;

pageHeight -= HeaderSize.Height;

groupFooterControl.Content = new HeaderFooterDataContext(parentDataContext, group.Items);

RowDefinition row = new RowDefinition();

row.Height = new GridLength(FooterSize.Height, GridUnitType.Pixel);

grid.RowDefinitions.Add(row);

grid.Children.Add(groupFooterControl);

Grid.SetRow(groupFooterControl, grid.RowDefinitions.Count - 1);

}

else

{

list.Remove(list[list.Count - 1]);

itemIndex--;

(listview.ItemsSource as CollectionView).Refresh();

groupPage++;

}

}

else

{

groupPage++;

}

grid.Children.Add(listview);

Grid.SetRow(listview, listViewRowIndex);

return grid;

}

First things first. The first thing that needs attention is the parameters of the method.

object parentDataContext: Well, it is the data context of the main control. Why we need this? Sometimes, the report designer needs other data sources in the headers or footers. Using this property, he/she can access the data of the main data context. Using this technique we don’t need to define properties for additional data and things like that.

CollectionView sourceView: It is the collectionView that contains the Groups.

ViewBase view: It is the template of the Report Body.

int itemsApproximation: ItemApproximation specifies the average number of items in the given page. Gradually adding items to the ListView and calculating its size is time consuming. This property helps the method to save time.

ref int itemIndex: The index of the first item that should be displayed in this page.

Rect size: The phisical size of the Group in this page.

out BindingList<object> list: The caller of this method needs the items in the ListView to pass it to the PageFooter and PageHeader. So the method should return it as a list.

In the first section of the method, there are some initilization and the layout of the group which is a Grid, has been created. listViewRowIndex specifies the Grid Row of the body/ListView. Why it is needed? Due to existance of footers and headers, the row index of the body/ListView would be changed. Insertion of all footers and headers depend on the location of the body/ListView. So we should keep it in a variable.

In the above section, the Header of the group would be inserted, in case that it is the first page of the group.

The page footer and the page header of the report need the data of the ListView. And the ListView needs the available size of the group, in case of existance of the group page footers and group page headers. So, in the first step, the available space for the ListView will be determined, then the ListView will be created and finally the Group Page Headers and Group Page Footers will be added to the group.

The last part of the method needs furthur attention. It is about adding the group footer. A group footer should be added to the report after finishing of each group. But what would happen if the current group has been finished and on the other hand, The ListView used the whole available space in the page. In such case, the last item of the ListView will be removed from the List and the group will be extended by one page and its Footer will be added to the last page.

Using the Code

Using the code is simple and straight-forward. It just needs defining the Group, Footers, Headers and View content. Here is the code of the Demo project in the attached code.

<Window x:Class="ICP.Controls.PrintableListView.Demo.MainWindow "

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:icp="clr-namespace:ICP.Controls.PrintableListView;assembly=ICP.Controls.PrintableListView"

xmlns:con="clr-namespace:ICP.Controls.PrintableListView.Demo"

Title="Window1" Height="600" Width="800">

<Window.Resources>

<CollectionViewSource x:Key='src'

Source="{Binding MyData}">

<CollectionViewSource.GroupDescriptions>

<PropertyGroupDescription PropertyName="Name" />

</CollectionViewSource.GroupDescriptions>

</CollectionViewSource>

<con:ListConverter x:Key="listConverter"></con:ListConverter>

<con:NameConverter x:Key="nameConverter"></con:NameConverter>

<con:SumConverter x:Key="sumConverter"></con:SumConverter>

<con:CountConverter x:Key="countConverter"></con:CountConverter>

<con:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"></con:BooleanToVisibilityConverter>

</Window.Resources>

<Grid>

<Grid.RowDefinitions>

<RowDefinition Height="Auto"></RowDefinition>

<RowDefinition Height="*"></RowDefinition>

</Grid.RowDefinitions>

<Button Grid.Row="0" Width="100" Content="Print" Command="{Binding ElementName=listview, Path=PrintCommand}"></Button>

<icp:PrintableListView PageSize="793.247244094488,1122.70866141732" HeaderSize="300,100" FooterSize="300,180" Grid.Row="1" Name="listview" ItemsSource='{Binding Source={StaticResource src}}'

BorderThickness="0">

<icp:PrintableListView.ReportBody>

<GridView>

<GridViewColumn Header="ID"

DisplayMemberBinding="{Binding Path=ID}"

Width="100" />

<GridViewColumn Header="Name"

DisplayMemberBinding="{Binding Path=Name}"

Width="240" />

<GridViewColumn Header="Price"

DisplayMemberBinding="{Binding Path=Price}"

Width="200" />

</GridView>

</icp:PrintableListView.ReportBody>

<icp:PrintableListView.PageHeaderTemplate>

<DataTemplate>

<Grid Margin="5" >

<Grid.RowDefinitions>

<RowDefinition Height="*"></RowDefinition>

<RowDefinition Height="Auto"></RowDefinition>

</Grid.RowDefinitions>

<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Header" Foreground="Black" FontSize="30"></TextBlock>

<StackPanel Orientation="Horizontal" Grid.Row="1">

<TextBlock FontSize="20" Text="Page Number:"></TextBlock>

<TextBlock FontSize="20" Text="{Binding PageNumber}"></TextBlock>

</StackPanel>

</Grid>

</DataTemplate>

</icp:PrintableListView.PageHeaderTemplate>

<icp:PrintableListView.PageFooterTemplate>

<DataTemplate>

<Grid Margin="5" >

<Grid.RowDefinitions>

<RowDefinition Height="Auto"></RowDefinition>

<RowDefinition Height="Auto"></RowDefinition>

</Grid.RowDefinitions>

<TextBlock Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Footer" Foreground="Black" FontSize="20"></TextBlock>

<StackPanel Orientation="Horizontal" Grid.Row="1">

<TextBlock FontSize="20" Text="Page Number:"></TextBlock>

<TextBlock FontSize="20" Text="{Binding PageNumber}"></TextBlock>

</StackPanel>

</Grid>

</DataTemplate>

</icp:PrintableListView.PageFooterTemplate>

<icp:PrintableListView.GroupStyle>

<GroupStyle>

<GroupStyle.ContainerStyle>

<Style TargetType="{x:Type GroupItem}">

<Setter Property="Margin" Value="0,0,0,5"/>

<Setter Property="Template">

<Setter.Value>

<ControlTemplate TargetType="{x:Type GroupItem}">

<Expander IsExpanded="True" BorderBrush="#FFA4B97F"

BorderThickness="0,0,0,1">

<Expander.Header>

<DockPanel>

<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"

Margin="5,0,0,0" Width="100"/>

</DockPanel>

</Expander.Header>

<Expander.Content>

<ItemsPresenter></ItemsPresenter>

</Expander.Content>

</Expander>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

</GroupStyle.ContainerStyle>

</GroupStyle>

</icp:PrintableListView.GroupStyle>

<icp:PrintableListView.Group>

<icp:ReportGroup FooterSize="30,30" HeaderSize="30,30" PageHeaderSize="30,30" PageFooterSize="30,30">

<icp:ReportGroup.GroupHeader>

<DataTemplate>

<StackPanel Orientation="Horizontal" Margin="5,0,5,0">

<TextBlock VerticalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="2" Text="{Binding PageItems, Converter={StaticResource nameConverter}}"></TextBlock>

<TextBlock VerticalAlignment="Center" Margin="2" Text="Count:"></TextBlock>

<TextBlock VerticalAlignment="Center" Margin="2" Text="{Binding PageItems, Converter={StaticResource countConverter}}"></TextBlock>

<TextBlock VerticalAlignment="Center" Margin="2" Text="Sum:"></TextBlock>

<TextBlock VerticalAlignment="Center" Margin="2" Text="{Binding PageItems, Converter={StaticResource sumConverter}}"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportGroup.GroupHeader>

<icp:ReportGroup.GroupFooter>

<DataTemplate>

<TextBlock Text="Group Footer"></TextBlock>

</DataTemplate>

</icp:ReportGroup.GroupFooter>

<icp:ReportGroup.GroupPageFooter>

<DataTemplate>

<StackPanel Visibility="{Binding IsLastPage, Converter={StaticResource booleanToVisibilityConverter}, ConverterParameter='false'}" Orientation="Horizontal">

<TextBlock FontWeight="Bold" Text="Name:" Margin="3"></TextBlock>

<TextBlock Text="{Binding PageItems, Converter={StaticResource nameConverter}}" Margin="3"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportGroup.GroupPageFooter>

<icp:ReportGroup.GroupPageHeader>

<DataTemplate>

<StackPanel Visibility="{Binding IsFirstPage, Converter={StaticResource booleanToVisibilityConverter}, ConverterParameter='false'}" Orientation="Horizontal">

<TextBlock FontWeight="Bold" Text="Name:" Margin="3"></TextBlock>

<TextBlock Text="{Binding PageItems, Converter={StaticResource nameConverter}}" Margin="3"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportGroup.GroupPageHeader>

<icp:ReportGroup.GroupDescriptions>

<PropertyGroupDescription PropertyName="Name" />

</icp:ReportGroup.GroupDescriptions>

</icp:ReportGroup>

</icp:PrintableListView.Group>

<icp:PrintableListView.View>

<GridView>

<GridViewColumn Header="ID" DisplayMemberBinding="{Binding Path=ID}" Width="100" />

<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" Width="140" />

<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Path=Price}" Width="80" />

</GridView>

</icp:PrintableListView.View>

</icp:PrintableListView>

</Grid>

</Window>

Future Works

In this paper I extended the report engine to support grouping, but the report engine still suffers from many deficiencies. First of all, its ReportGroup needs SubGroup property, so that report designer could define countless groups. Secondly, the Page Layouts and Group Layouts are hardcoded. Report designer should be able to provide custom page and group layout. Finally, it needs a report viewer. In the next version, I will target these issues.

You can downlaod the code here

By Siyamand Ayubi   Popularity  (6259 Views)