WPF Report Engine, Part 3

In the previous papers, (Part 1 and Part 2), I provided a mechanism to print WPF lists in the form of a report that supports Headers, Footers, PageHeaders PageFooters and grouping. In this paper, I will expand the previous implementation to look like a real report engine that covers almost 80% of the print requirements except charting and graphing.

Part 1
Part 2
Part 3
Part 4

The Story So Far

The story began by posing the problem of printing a ListView in such a way that the printing process takes into account the pagination. Although, WPF has the capability of printing any Visual, however it does nothing about pagination. It means if the visual exceeds one page, the over headed area would be excluded from the printed version. In the first part of this series, I showed an approach for the problem using custom DocumentPaginator. DocumentPaginator provides an abstract base class that supports creation of multiple-page elements from a single document. My custom DocumentPaginator created a ListView per page and then filled it by the ItemsSource of the original ListView until its size reached the end of the page. Filling the next page was started from the end position of the previous ListView. This process repeats until there is no data in the ItemsSource. Interested reader can read the first part of this series for further detail.

In the second part of this series, I extended the approach to support grouping of data. The proposed method used the GroupDescription class of the .net framework to define custom groups on the data. The generated groups can have Headers, Footers, PageHeaders and PageFooters. User is able to provide custom footers or headers using DataTemplates. Limiting to one group section is the main restriction of the previous version.

In this paper, I will expand the previous implementation to cover more complicated problems. The new implementation looks like a real report engine that covers almost 80% of the print requirements except charting and graphing. The new code involves lots of refactoring. The benefits of the new version can be summarized as follows:

Improvement

  • The ability to view the reports in a ReportViewer.
  • The ability to define custom layouts for the reports including any number of groups, sub groups and etc..
  • Simplifying the code by removing code redundancies.
  • The ability to view the reports as XPS documents.
  • The ability to dynamically deploy the reports.
Refactoring

Although, the story began by providing a mechanism for printing a ListView, now, it is far more than printing a ListView. We deal with a report engine now, so the first refactoring is renaming PrintableListView to Report. Also there is no reason to inherit the Report from the ListView, instead it should inherit from the more general class, ItemsControl.

In the previous implementation, both of the Report and ReportGroup classes have the Header, PageHeader, Footer and PageFooter properties. On the other hand, there was some kind of code redundancy in the previous implementation. The better architecture is having a ReportSection class that contains those properties. The ReportGroup inherits from it and the Report class has an instance of it in its MainSection property. In the previous implementation, the rendering code was distributed in the Report and ReportGroup classes. In the current implementation, there are classes called ListViewSection and ItemsControlSection that are responsible for rendering the main data. The following diagram illustrates the architecture of the new version.


In the following section, I elaborate the classes in more detail.

Report Class

This class is the entry point of a report. In the new version, it does not have any special rendering code. All of the layout definition and rendering process has been done in its MainSection property that will be described later. Its PageSize property gets the size of the printed pages in pixels. Each pixel is 1/96 of an inch. Its Document property is new in this version. It contains the output of the rendering process and its type is XPSDocument. ReportViewer uses it to show the report.

Section Class

The instances of this class are the blocking parts of the report. Although the class itself is an abstract class, but there are several concrete implementations of it such as ReportSection, ReportGroup, ListViewSection and ItemsControlSection. Among them ReportSection and ReportGroup are composite classes. By composite class, I mean they have a child property of type Section that is called Body. Composite Sections can be used to cover other sections. Here is the code of the Section class.

public abstract class Section : DependencyObject

{

#region Methods

public abstract void Render(Rect size, int reportPageNumber);

public abstract void ShrinkData();

public abstract bool ShrinkControl(double minimumHeightToReduce);

public abstract void Initialize(object mainSource, object parentDataContext);

#endregion

#region Properties

internal int itemIndex;

internal FrameworkElement CurrentPageBody;

internal object CurrentPageDataSource;

internal virtual bool IsEof

{

get

{

return isEof;

}

set

{

isEof = value;

}

}

protected bool isEof;

#endregion

}

The Initialize method of the class has been called by the parent section or by the report before generating printed pages. The Render method generates the content of the current page. It puts the generated content of the current page in the CurrentPageBody property of the class. The CurrentPageDataSource contains the data source of the current page. The CurrentPageBody and CurrentPageDataSource have been refreshed after each call of the Render method. The ShrinkData and ShrinkControl methods have been used by the parent sections to reduce the size of the CurrentPageBody. The shrinking process is needed in situations where there is not enough space for adding the footer content. It is happen because the parent section has no idea about adding the footer before rendering its child. Only after rendering the current child section it becomes clear. In such cases, if there is not enough space for the footer, then the size of the child section should be reduced using the Shrink methods. ItemIndex property specifies the index of the current item of the underlying data source. The Render method starts adding the items to the CurrentPageBody from the ItemIndex in each page. IsEof specifies the ending of the underlying data source. The Render method updates IsEof value when it reached the end of the data source.

ItemsControlSection and ListViewSection

These two concrete implementations of the Section class generate list base contents. Their Render methods create list base contents using the provided data source. ListViewSection generates a ListView per page and ItemsControlSection generates an ItemsControl per page. The real shrinking process has been done in these classes. The ShrinkControl method tries to reduce the size of generated content without removing any data from the content. The ShrinkData method reduces the size of the rendered content by removing some data items from them. The removed data will be used in the next page. Here is the code of ItemsControlSection. The code of ListViewSection is similar.

public class ItemsControlSection : Section

{

#region Properties

public DataTemplate ItemTemplate

{

get

{

return (DataTemplate)this.GetValue(ItemTemplateProperty);

}

set

{

this.SetValue(ItemTemplateProperty, value);

}

}

public static DependencyProperty ItemTemplateProperty =

DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(ItemsControlSection), new PropertyMetadata());

#endregion

#region Privates

private IList sourceView;

private object parentDataContext;

private double listviewRealSize;

#endregion

#region Methods

public override void Render(Rect size, int reportPageNumber)

{

ItemsControl itemsControl = new ItemsControl();

ScrollViewer.SetCanContentScroll(itemsControl, false);

ScrollViewer.SetVerticalScrollBarVisibility(itemsControl, ScrollBarVisibility.Disabled);

// Setting view

if (ItemTemplate != null)

{

itemsControl.ItemTemplate = ItemTemplate;

itemsControl.UpdateLayout();

}

// Create Itemssource

BindingList<object> list = new BindingList<object>();

BindingListCollectionView collectionViewSource = new BindingListCollectionView(list);

itemsControl.ItemsSource = collectionViewSource;

//listview.GroupStyle.Add(Report.PrintGroupStyle);

StackPanel body = new StackPanel();

body.Children.Add(itemsControl);

// add first item to get rough estimation of number of items per page

object item = sourceView[itemIndex];

list.Add(item);

itemIndex++;

itemsControl.Measure(new Size());

body.Arrange(new Rect());

int itemsApproximation = 20;

if (itemsControl.ActualHeight > 0)

{

itemsApproximation = Convert.ToInt32(Math.Floor(size.Height / itemsControl.ActualHeight));

}

// Add the estimated items to the listview

int currentPosition = itemIndex;

for (int i = 0; i < itemsApproximation && i + currentPosition < sourceView.Count; i++)

{

item = sourceView[itemIndex];

list.Add(item);

itemIndex++;

}

collectionViewSource.Refresh();

// Calculate the size of listview

itemsControl.Measure(new Size());

body.Arrange(new Rect());

// Recorrect the items inside listview

while (itemsControl.ActualHeight < size.Height && itemIndex < sourceView.Count)

{

item = sourceView[itemIndex];

list.Add(item);

itemIndex++;

collectionViewSource.Refresh();

itemsControl.Measure(new Size());

body.Arrange(size);

}

while (itemsControl.ActualHeight > size.Height && itemIndex >= 0)

{

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

itemIndex--;

collectionViewSource.Refresh();

itemsControl.Measure(new Size());

body.Arrange(size);

}

body.Children.Clear();

if (itemIndex >= sourceView.Count - 1)

{

IsEof = true;

}

listviewRealSize = itemsControl.ActualHeight;

CurrentPageDataSource = list;

CurrentPageBody = itemsControl;

}

public override bool ShrinkControl(double minimumToReduce)

{

ListView listView = CurrentPageBody as ListView;

if (listviewRealSize + minimumToReduce < listView.ActualHeight)

{

listView.Height = listView.ActualHeight - minimumToReduce;

listView.Measure(new Size(listView.ActualWidth, listView.Height));

return true;

}

return false;

}

public override void ShrinkData()

{

IList list = CurrentPageDataSource as IList;

ListView listView = CurrentPageBody as ListView;

list.RemoveAt(list.Count - 1);

itemIndex--;

IsEof = false;

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

}

public override void Initialize(object mainSource, object parentDataContext)

{

sourceView = mainSource as IList;

this.parentDataContext = parentDataContext;

itemIndex = 0;

IsEof = false;

}

#endregion

}

ReportSection and ReportGroup
ReportSection and ReportGroup classes are two implementation of the Section class that can have a child section. ReportSection has Header, Footer, PageHeader and PageFooter properties and is useful in situations where one wants to create headers and footers for a simple section like ItemsControlSection. ReportGroup section inherits from a ReportSection and provides grouping functionality for the data source. Grouping of the data in a ReportGroup has been done using grouping feature of the CollectionView class. Here is the code of the classes.

public class ReportSection : Section

{

#region Properties

public Section Body

{

get

{

return (Section)this.GetValue(BodyProperty);

}

set

{

this.SetValue(BodyProperty, value);

}

}

public static DependencyProperty BodyProperty =

DependencyProperty.Register("Body", typeof(Section), typeof(ReportSection), new PropertyMetadata());

public DataTemplate Footer

{

get

{

return (DataTemplate)this.GetValue(FooterProperty);

}

set

{

this.SetValue(FooterProperty, value);

}

}

public static DependencyProperty FooterProperty =

DependencyProperty.Register("Footer", typeof(DataTemplate), typeof(ReportSection), new PropertyMetadata());

public DataTemplate Header

{

get

{

return (DataTemplate)this.GetValue(HeaderProperty);

}

set

{

this.SetValue(HeaderProperty, value);

}

}

public static DependencyProperty HeaderProperty =

DependencyProperty.Register("Header", typeof(DataTemplate), typeof(ReportSection), new PropertyMetadata());

public DataTemplate PageFooter

{

get

{

return (DataTemplate)this.GetValue(PageFooterProperty);

}

set

{

this.SetValue(PageFooterProperty, value);

}

}

public static DependencyProperty PageFooterProperty =

DependencyProperty.Register("PageFooter", typeof(DataTemplate), typeof(ReportSection), new PropertyMetadata());

public DataTemplate PageHeader

{

get

{

return (DataTemplate)this.GetValue(PageHeaderProperty);

}

set

{

this.SetValue(PageHeaderProperty, value);

}

}

public static DependencyProperty PageHeaderProperty =

DependencyProperty.Register("PageHeader", typeof(DataTemplate), typeof(ReportSection), 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(ReportSection), 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(ReportSection), 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(ReportSection), 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(ReportSection), new PropertyMetadata());

internal override bool IsEof

{

get

{

return Body.IsEof;

}

set

{

Body.IsEof = value;

}

}

#endregion

#region Methods

public override void Render(Rect size, int reportPageNumber)

{

// The index of the grid row of the ListView

int bodyRowIndex = 0;

double pageHeight = size.Height;

// The layout of the group

Grid grid = new Grid();

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

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

// Add header

if (currentPage == 0 && Header != null)

{

ContentControl HeaderControl = new ContentControl();

HeaderControl.ContentTemplate = Header;

HeaderControl.Height = HeaderSize.Height;

pageHeight -= HeaderSize.Height;

HeaderControl.Content = new ReportDataContext(parentDataContext, getCurrentItemsSource() as IList);

RowDefinition row = new RowDefinition();

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

grid.RowDefinitions.Insert(0, row);

grid.Children.Add(HeaderControl);

Grid.SetRow(HeaderControl, 0);

bodyRowIndex++;

}

// Page header

if (PageHeader != null)

{

pageHeight -= this.PageHeaderSize.Height;

}

// Page footer

if (PageFooter != null)

{

pageHeight -= this.PageFooterSize.Height;

}

Body.Render(new Rect(size.Left, size.Top, size.Width, pageHeight), reportPageNumber);

ReportDataContext reportSectionDataContext = new ReportDataContext(parentDataContext, Body.CurrentPageDataSource as IList);

reportSectionDataContext.PageNumber = reportPageNumber;

// group page header

if (PageHeader != null)

{

ContentControl groupPageHeaderControl = new ContentControl();

groupPageHeaderControl.ContentTemplate = PageHeader;

groupPageHeaderControl.Height = this.PageFooterSize.Height;

groupPageHeaderControl.Content = reportSectionDataContext;

RowDefinition row = new RowDefinition();

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

grid.RowDefinitions.Insert(bodyRowIndex, row);

grid.Children.Add(groupPageHeaderControl);

Grid.SetRow(groupPageHeaderControl, bodyRowIndex);

bodyRowIndex++;

}

// group page footer

if (PageFooter != null)

{

ContentControl PageFooterControl = new ContentControl();

PageFooterControl.ContentTemplate = PageFooter;

PageFooterControl.Height = this.PageFooterSize.Height;

PageFooterControl.Content = reportSectionDataContext;

RowDefinition row = new RowDefinition();

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

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

grid.Children.Add(PageFooterControl);

Grid.SetRow(PageFooterControl, bodyRowIndex + 1);

}

// Group footer

if (Body.IsEof)

{

FrameworkElement FooterControl = null;

if (currentItemSourceReachesEnd(Body.CurrentPageBody, out FooterControl, reportSectionDataContext, size.Height))

{

if (FooterControl != null)

{

RowDefinition row = new RowDefinition();

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

grid.RowDefinitions.Add(row);

grid.Children.Add(FooterControl);

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

}

}

}

else

{

currentPage++;

}

grid.Children.Add(Body.CurrentPageBody);

Grid.SetRow(Body.CurrentPageBody, bodyRowIndex);

grid.Arrange(size);

CurrentPageBody = grid;

CurrentPageDataSource = Body.CurrentPageDataSource;

}

protected virtual object getCurrentItemsSource()

{

return mainSource;

}

protected virtual bool currentItemSourceReachesEnd(FrameworkElement body, out FrameworkElement footer, ReportDataContext headerFooterContent, double pageHeight)

{

headerFooterContent.IsLastPage = true;

if (Footer != null && body.ActualHeight + FooterSize.Height >= pageHeight)

{

ShrinkControl(FooterSize.Height);

}

else if (Footer == null)

{

footer = null;

return true;

}

if (Footer != null &&

body.ActualHeight + FooterSize.Height <= pageHeight)

{

itemIndex = 0;

this.IsEof = true;

ContentControl FooterControl = new ContentControl();

FooterControl.ContentTemplate = Footer;

FooterControl.Height = FooterSize.Height;

pageHeight -= HeaderSize.Height;

FooterControl.Content = new ReportDataContext(parentDataContext, getCurrentItemsSource() as IList);

RowDefinition row = new RowDefinition();

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

footer = FooterControl;

return true;

}

else

{

Body.ShrinkData();

footer = null;

return false;

}

}

public override bool ShrinkControl(double minimumToReduce)

{

if (Body.ShrinkControl(minimumToReduce))

{

CurrentPageBody.Arrange(new Rect(0, 0, CurrentPageBody.ActualWidth, CurrentPageBody.ActualHeight - minimumToReduce));

return true;

}

return false;

}

public override void ShrinkData()

{

Body.ShrinkData();

}

public override void Initialize(object parentDataContext, object mainSource)

{

this.mainSource = mainSource;

this.parentDataContext = parentDataContext;

Body.Initialize(parentDataContext, mainSource);

IsEof = false;

}

#endregion

#region Privates

private object mainSource;

protected object parentDataContext;

protected int currentPage;

#endregion

}

public class ReportGroup : ReportSection

{

#region Constructor

public ReportGroup()

{

GroupDescriptions = new ObservableCollection<GroupDescription>();

}

#endregion

#region 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());

internal int groupIndex;

protected ListCollectionView sourceView;

internal override bool IsEof

{

get

{

return groupIndex >= sourceView.Groups.Count;

}

}

#endregion

#region Methods

public override void Initialize(object mainSource, object parentDataContext)

{

this.parentDataContext = parentDataContext;

CollectionViewSource source = new CollectionViewSource();

source.Source = mainSource;

if (GroupDescriptions != null)

{

foreach (GroupDescription groupDescription in GroupDescriptions)

{

source.GroupDescriptions.Add(groupDescription);

}

}

isEof = false;

groupIndex = 0;

currentPage = 0;

sourceView = source.View as ListCollectionView;

if (sourceView.Groups.Count > 0)

{

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

Body.Initialize(group.Items, parentDataContext);

}

else

{

Body.Initialize(mainSource, parentDataContext);

}

}

protected override object getCurrentItemsSource()

{

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

return group.Items;

}

protected override bool currentItemSourceReachesEnd(FrameworkElement body, out FrameworkElement footer, ReportDataContext headerFooterContent, double pageHeight)

{

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

headerFooterContent.IsLastPage = true;

if (Footer != null && body.ActualHeight + FooterSize.Height >= pageHeight)

{

ShrinkControl(FooterSize.Height);

}

if ( body.ActualHeight + FooterSize.Height <= pageHeight)

{

currentPage = 0;

groupIndex++;

itemIndex = 0;

if (groupIndex < sourceView.Groups.Count)

{

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

Body.Initialize(newGroup.Items, parentDataContext);

}

if (Footer != null)

{

ContentControl FooterControl = new ContentControl();

FooterControl.ContentTemplate = Footer;

FooterControl.Height = FooterSize.Height;

pageHeight -= HeaderSize.Height;

FooterControl.Content = new ReportDataContext(parentDataContext, group.Items);

footer = FooterControl;

}

else

{

footer = null;

}

return true;

}

else

{

Body.ShrinkData();

currentPage++;

footer = null;

return false;

}

}

#endregion

}

Rendering process of the ReprotGroup class is more complicated than other sections. In order to simplify its logic for the reader, I draw the following flowchart that illustrates the process of rendering a ReportGroup class.

ReportViewer

In this version, there is a simple ReportViewer that is based on the DocumentViewer class of the .Net framework. DocumentViewer Represents a document viewing control that can host paginated FixedDocument content such as an XpsDocument. In order to make our reports consistent with the DocumentViewer, the reports should render their content as XpsDocuments. An XpsDocument contains a FixedDocumentSequence that comprises one or more FixedDocument elements. The following code renders the report as an XpsDocument.

public virtual FixedDocument CreateFixeDocumentPages()

{

FixedDocument document = new FixedDocument();

int count = 0;

while (!Report.MainSection.IsEof)

{

Rect rect = new Rect(0, 0, PageSize.Width - PageMargin.Left - PageMargin.Right, pageHeight - PageMargin.Top - PageMargin.Bottom);

Report.MainSection.Render(rect, count);

Report.MainSection.CurrentPageBody.Height = Report.MainSection.CurrentPageBody.ActualHeight;

Report.MainSection.CurrentPageBody.Width = Report.MainSection.CurrentPageBody.ActualWidth;

PageContent page = new PageContent();

FixedPage fixedPage = new FixedPage();

fixedPage.Width = Report.MainSection.CurrentPageBody.ActualWidth;

fixedPage.Height= Report.MainSection.CurrentPageBody.ActualHeight;

fixedPage.Children.Add(Report.MainSection.CurrentPageBody);

((IAddChild)page).AddChild(fixedPage);

page.Arrange(rect);

document.Pages.Add(page);

count++;

}

return document;

}

Here is the code of the ReportViewer.

public class ReportViewer : DocumentViewer

{

public Report Report

{

get { return (Report)GetValue(ReportProperty); }

set { SetValue(ReportProperty, value); }

}

// Using a DependencyProperty as the backing store for Report. This enables animation, styling, binding, etc...

public static readonly DependencyProperty ReportProperty =

DependencyProperty.Register("Report", typeof(Report), typeof(ReportViewer), new UIPropertyMetadata(null, onReportChanged));

public ReportViewer()

{

}

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)

{

base.OnPropertyChanged(e);

if (e.Property.Name == "DataContext" && Report != null)

{

Report.DataContext = e.NewValue;

}

}

private static void onReportChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)

{

Report report = e.NewValue as Report;

if (report == null)

{

return;

}

ReportViewer viewer = obj as ReportViewer;

Binding binding = new Binding("Document");

binding.Source = report;

binding.Mode = BindingMode.TwoWay;

viewer.SetBinding(ReportViewer.DocumentProperty, binding);

report.DataContext = viewer.DataContext;

}

}

Using the Code

The attached code contains a simple report that has two ReportGroup section. The first group, groups the data by the Country property and the second group groups the items of each country based on their OwnerName property. Here is the XAML code of the report.

<icp:ReportViewer Grid.Row="0" >

<icp:ReportViewer.Report>

<icp:Report PageSize="793.247244094488,1122.70866141732" Name="listview" ItemsSource="{Binding MyData}">

<icp:Report.MainSection>

<icp:ReportSection HeaderSize="300,50" FooterSize="300,50" PageHeaderSize="300,50" PageFooterSize="200,50">

<icp:ReportSection.Body>

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

<icp:ReportGroup.GroupDescriptions>

<PropertyGroupDescription PropertyName="Country" />

</icp:ReportGroup.GroupDescriptions>

<icp:ReportGroup.Header>

<DataTemplate>

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

<TextBlock FontSize="20" Text="CountryGroup Header" Margin="3"></TextBlock>

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.Header>

<icp:ReportGroup.Footer>

<DataTemplate>

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

</DataTemplate>

</icp:ReportGroup.Footer>

<icp:ReportGroup.PageFooter>

<DataTemplate>

<StackPanel Background="Yellow" Orientation="Horizontal">

<TextBlock FontSize="20" Text="CountryGroup Page Footer" Margin="3"></TextBlock>

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

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.PageFooter>

<icp:ReportGroup.PageHeader>

<DataTemplate>

<StackPanel Background="Yellow" Orientation="Horizontal">

<TextBlock FontSize="20" Text="CountryGroup Page Header" Margin="3"></TextBlock>

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

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.PageHeader>

<icp:ReportGroup.Body>

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

<icp:ReportGroup.Header>

<DataTemplate>

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

<TextBlock VerticalAlignment="Center" FontSize="24" Margin="2" Text="NameGroup Header"></TextBlock>

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.Header>

<icp:ReportGroup.Footer>

<DataTemplate>

<TextBlock FontSize="24" Text="NameGroup Footer"></TextBlock>

</DataTemplate>

</icp:ReportGroup.Footer>

<icp:ReportGroup.PageFooter>

<DataTemplate>

<StackPanel Background="Yellow" Orientation="Horizontal">

<TextBlock VerticalAlignment="Center" FontSize="24" Margin="2" Text="NameGroup Page Footer"></TextBlock>

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

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.PageFooter>

<icp:ReportGroup.PageHeader>

<DataTemplate>

<StackPanel Background="Yellow" Orientation="Horizontal">

<TextBlock VerticalAlignment="Center" FontSize="24" Margin="2" Text="NameGroup Page Header"></TextBlock>

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

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

</StackPanel>

</DataTemplate>

</icp:ReportGroup.PageHeader>

<icp:ReportGroup.GroupDescriptions>

<PropertyGroupDescription PropertyName="OwnerName" />

</icp:ReportGroup.GroupDescriptions>

<icp:ReportGroup.Body>

<icp:ListViewSection>

<icp:ListViewSection.Body>

<GridView>

<GridViewColumn Header="Id"

DisplayMemberBinding="{Binding Path=ItemID}"

Width="140" />

<GridViewColumn Header="Owner Name"

DisplayMemberBinding="{Binding Path=OwnerName}"

Width="140" />

<GridViewColumn Header="Country"

DisplayMemberBinding="{Binding Path=Country}"

Width="140" />

<GridViewColumn Header="Price"

DisplayMemberBinding="{Binding Path=Price}"

Width="140" />

</GridView>

</icp:ListViewSection.Body>

</icp:ListViewSection>

</icp:ReportGroup.Body>

</icp:ReportGroup>

</icp:ReportGroup.Body>

</icp:ReportGroup>

</icp:ReportSection.Body>

<icp:ReportSection.Header>

<DataTemplate>

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

<TextBlock VerticalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="2" Text="Report Main Header"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportSection.Header>

<icp:ReportSection.Footer>

<DataTemplate>

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

<TextBlock VerticalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="2" Text="Report Main Footer"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportSection.Footer>

<icp:ReportSection.PageFooter>

<DataTemplate>

<StackPanel Orientation="Horizontal">

<TextBlock VerticalAlignment="Center" FontSize="20" Text="Report Page Footer"></TextBlock>

<TextBlock VerticalAlignment="Center" FontSize="12" Text="Page Number:"></TextBlock>

<TextBlock VerticalAlignment="Center" FontSize="12" Text="{Binding PageNumber}"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportSection.PageFooter>

<icp:ReportSection.PageHeader>

<DataTemplate>

<StackPanel Background="Ivory" Orientation="Horizontal">

<TextBlock VerticalAlignment="Center" FontSize="20" Text="Report Page Header"></TextBlock>

<TextBlock VerticalAlignment="Center" FontSize="12" Text="Page Number:"></TextBlock>

<TextBlock VerticalAlignment="Center" FontSize="12" Text="{Binding PageNumber}"></TextBlock>

</StackPanel>

</DataTemplate>

</icp:ReportSection.PageHeader>

</icp:ReportSection>

</icp:Report.MainSection>

</icp:Report>

</icp:ReportViewer.Report>

</icp:ReportViewer>

Future Works

In the next paper in this series, I will show the power of the report engine in practical application by providing different report example in different scenarios.

The code can be downloaded here.

By Siyamand Ayubi   Popularity  (4241 Views)