Introduction to Silverlight Reporting

[Infragistics] Mihail Mateev / Thursday, July 1, 2010

This article demonstrates how can use the new Silverlight Reporting project.

It is possible to download source code and sample demos from Codeplex here

This project, a created from Pete Brown and David Poll of Microsoft, provides a a very basic framework for building simple, multi-page reports using Silverlight 4.

The initial release includes:

  • automatic pagination
  • support for line items of varying height
  • total page count
  • templating
  • page headers and footers
  • report footer with support for calculated fields
  • events to allow hooking into printing at various stages

 Silverlight Reporting uses ItemsSource from IEnumerable type. It is not related to specific UI component.

Silverlight Reporting demo application presents Silverlight Navigation application , that
uses data from Silverlight WCF Services.

In different pages are placed Microsoft DataGrid and Infargistics Silverlight XamGrid.
Both controls use like ItemsSource data from WCF Services.

Silverlight Reporting is included in a simple ViewModel, that implements data loading and printing,
using Silverlight Commanding.

 Requirements

  1. If you haven't latest build of Silverlight 4 Tools, you can download it from here :
  2. Download the latest version of the Silverlight Reporting from CodePlex
  3. Download NetAdvantage for Silverlight Line of Business 10.2 (Optional).

Steps to implement the application.

  1. Create a new Silverlight Navigation Application.
  2. Implement a Silverlight WCF Service, that send a data to the Silverlight client.
  3. Create a reference to this service in the client.
  4. Reference the Silverlight Reporting assembly in the Silverlight application.
  5. Create a Silverlight Resource Dictionary with reporting template for our report.
  6. Create a simple ViewModel that implements data loading from WCF Service and
    printing.
  7. Create two pages in the Navigation application - first with Microsoft DataGrid in
    the layout and second - with the Infragistics XamGrid.
  8. Add this  ViewModel  as a static resource in these two pages and add it as s DataContext for
    each page layout.
  9. Bind the ItemsSource of the both grids to the Products property of the ViewModel.
  10. Implement buttons for data loading and printing using the Silverlight Commanding.

Create a Silverlight Navigation Application

Implement a Silverlight WCF Service and reference it in the client. (Implementation is in the demo application).

Reference Silverlight.Reporting assembly

 Create a Silverlight Resource Dictionary with reporting template:

There are four template properties (each of type DataTemplate) in play in the image above:

  • PageHeaderTemplate
  • PageFooterTemplate
  • ItemTemplate
  • ReportFooterTemplate

   <ResourceDictionary


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


   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:reporting="clr-namespace:System.Windows.Printing.Reporting;assembly=Silverlight.Reporting">


    <reporting:Report x:Key="Report"


                     Title="Products Review List">


 


        <reporting:Report.Resources>


            <Style x:Key="HeaderText"


                      TargetType="TextBlock">


                <Setter Property="FontWeight"


                           Value="Bold" />


                <Setter Property="TextAlignment"


                           Value="Left" />


                <Setter Property="HorizontalAlignment"


                           Value="Stretch" />


            </Style>


        </reporting:Report.Resources>


 


        <reporting:Report.PageHeaderTemplate>


            <DataTemplate>


                <Grid Margin="1 1 1 20">


                    <Rectangle Stroke="Black" />


 


                    <Grid>


                        <Grid.RowDefinitions>


                            <RowDefinition Height="Auto" />


                            <RowDefinition Height="Auto" />


                        </Grid.RowDefinitions>


 


                        <TextBlock Text="{Binding Title}"


                                      Grid.Row="0"


                                      FontSize="16"


                                      FontWeight="Bold"


                                      Margin="5"


                                      HorizontalAlignment="Left"


                                      VerticalAlignment="Top" />


 


                        <TextBlock Text="{Binding CurrentPageNumber, StringFormat='Page {0}'}"


                                      Grid.Row="0"


                                      Margin="5"


                                      HorizontalAlignment="Right"


                                      VerticalAlignment="Top" />


                        <Grid Grid.Row="1"


                                 HorizontalAlignment="Stretch"


                                 Margin="4 0 0 4">


                            <Grid.RowDefinitions>


                                <RowDefinition Height="Auto" />


                                <RowDefinition Height="Auto" />


                            </Grid.RowDefinitions>


                            <Grid.ColumnDefinitions>


                                <ColumnDefinition Width="200" />


                                <ColumnDefinition Width="200" />


                                <ColumnDefinition Width="150" />


                                <ColumnDefinition Width="150" />


                            </Grid.ColumnDefinitions>


 


                            <TextBlock Grid.Column="0"


                                          Style="{StaticResource HeaderText}"


                                          Text="SKU and Name" />


 


                            <TextBlock Grid.Column="1"


                                          Style="{StaticResource HeaderText}"


                                          TextAlignment="Left"


                                          Text="Category" />


 


                            <TextBlock Grid.Column="2"


                                          Style="{StaticResource HeaderText}"


                                          TextAlignment="Right"


                                          Text="Supplier" />


 


                            <TextBlock Grid.Column="3"


                                          Style="{StaticResource HeaderText}"


                                          TextAlignment="Right"


                                          Text="UnitPrice" />


 


                            <TextBlock Grid.Row="2"


                                          Style="{StaticResource HeaderText}"


                                          Text="Units in Stock" />


 


                            <TextBlock Grid.Row="2" Grid.Column="1"


                                          Style="{StaticResource HeaderText}"


                                          Text="Quantity per Unit" />


                        </Grid>


                    </Grid>


                </Grid>


            </DataTemplate>


        </reporting:Report.PageHeaderTemplate>


 


        <reporting:Report.ItemTemplate>


            <DataTemplate>


                <Grid HorizontalAlignment="Stretch"


                         Margin="5 0 0 20">


                    <Grid.RowDefinitions>


                        <RowDefinition Height="Auto" />


                        <RowDefinition Height="Auto" />


                        <RowDefinition Height="Auto" />


                    </Grid.RowDefinitions>


                    <Grid.ColumnDefinitions>


                        <ColumnDefinition Width="200" />


                        <ColumnDefinition Width="200" />


                        <ColumnDefinition Width="150" />


                        <ColumnDefinition Width="150" />


                    </Grid.ColumnDefinitions>


 


                    <StackPanel Grid.Column="0"


                                   Orientation="Horizontal">


                        <TextBlock Text="{Binding SKU}"


                                      FontWeight="Bold" />


                        <TextBlock Text=", "


                                      FontWeight="Bold" />


                        <TextBlock Text="{Binding Name}"


                                      FontWeight="Bold" />


                    </StackPanel>


 


                    <TextBlock Grid.Column="1"


                                  Text="{Binding Category}"


                                  TextAlignment="Left" />


 


                    <TextBlock Grid.Column="2"


                                  Text="{Binding Supplier}"


                                  TextAlignment="Right" />


 


                    <TextBlock Grid.Column="3"


                                  Text="{Binding UnitPrice, StringFormat='{}{0:C}'}"


                                  TextAlignment="Right" />


 


                    <TextBlock Grid.Row="1" Text="{Binding UnitsInStock}" />


                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding QuantityPerUnit}" />


                    <TextBlock Grid.Row="2"


                                  Grid.ColumnSpan="4"


                                  TextWrapping="Wrap"


                                  Text="{Binding ReviewComments}" />


                </Grid>


            </DataTemplate>


        </reporting:Report.ItemTemplate>


 


        <reporting:Report.PageFooterTemplate>


            <DataTemplate>


                <Grid Margin="1 20 1 1">


                    <Rectangle Stroke="Black" />


                    <StackPanel Orientation="Horizontal"


                                   HorizontalAlignment="Right"


                                   VerticalAlignment="Center"


                                   Margin="5">


                        <TextBlock Text="{Binding CurrentPageNumber, StringFormat='Page {0}'}" />


                        <TextBlock Text="{Binding TotalPageCount, StringFormat=' of {0}'}" />


                      </StackPanel>


                  </Grid>


            </DataTemplate>


        </reporting:Report.PageFooterTemplate>


        <reporting:Report.ReportFooterTemplate>


            <DataTemplate>


                <Grid Margin="1 20 1 1">


                    <Rectangle Stroke="Black" />


                      <Grid>


                        <Grid.ColumnDefinitions>


                            <ColumnDefinition Width="200" />


                            <ColumnDefinition Width="200" />


                            <ColumnDefinition Width="150" />


                            <ColumnDefinition Width="150" />


                        </Grid.ColumnDefinitions>


                          <TextBlock Grid.Column="1"


                                      Text="{Binding ProductsCount, StringFormat='{}{0} products'}"


                                      TextAlignment="Left" />


                        <TextBlock Grid.Column="2"


                                      Text="{Binding TotalPrice, StringFormat='{}{0:C}'}"


                                      TextAlignment="Right" />


                    </Grid>


                </Grid>


            </DataTemplate>


        </reporting:Report.ReportFooterTemplate>


    </reporting:Report>


</ResourceDictionary>


Add this ResourceDictionary to Application.Resources:


   <Application.Resources>


    <ResourceDictionary>


      <ResourceDictionary.MergedDictionaries>


        <ResourceDictionary Source="Assets/Styles.xaml"/>


        <ResourceDictionary Source="Assets/ReportStyles.xaml"/>


      </ResourceDictionary.MergedDictionaries>


    </ResourceDictionary>


  </Application.Resources>


Create a simple ViewModel that implements data loading from WCF Service and
printing.


There is code from the ViewModel, related with reporting.

All code you can find in the sample application:

 

public class DataViewModel : BaseViewModel

{

        public DataViewModel()

        {

            _products = new List<Product>();

            _report = Application.Current.Resources["Report"] as Report;

 

            InitializeReport();

        }

...

 

       public List<Product> Products

        {

            get

            {

                return this._products;

            }

            set

            {

                if (this._products != value)

                {

                    this._products = value;

                    this.OnPropertyChanged("Products");

                }

            }

        }

....

 

 
        private void InitializeReport()

        {

            //_report = Application.Current.Resources["Report"] as Report;

            _report.EndPrint += (s, e) => MessageBox.Show("Report printed.");

 

            _report.BeginBuildReport += (s, e) =>

            {

                // initialize running totals

                _totals = new ReportTotals();

            };

 

            _report.BeginBuildReportItem += (s, e) =>

            {

                // this event fires with each report item, so you

                // can use it to build a running total

                var item = e.DataContext as Product;

 

                _totals.ProductsCount++;

                _totals.TotalPrice += item.UnitPrice * item.UnitsInStock;

 

            };

 

            _report.BeginBuildReportFooter += (s, e) =>

            {

                // set the running total as the context for the report footer

                e.DataContext = _totals;

            };

 

        }



 ...


        public void Print()

        {

            if (_products == null)

            {

                return;

            }

            this._report.Print();

        }

....


        public ICommand PrintReport

        {

            get { return new PrintReportCommand(this); }

        }

        #endregion //PrintReport



 

}

    public class PrintReportCommand : ICommand

    {

        private readonly DataViewModel _view;

 

        public PrintReportCommand(DataViewModel view)

        {

            _view = view;

        }

 

        public bool CanExecute(object parameter)

        {

            List<Product> products = parameter as List<Product>;

            if (products != null && products.Count > 0)

            {

                return true;

            }

            return false;

        }

 

        public event EventHandler CanExecuteChanged;

 

        public void Execute(object parameter)

        {

            _view.Print();

        }

    }

Create pages in the Navigation application - one with Microsoft DataGrid in
the layout and second - with the Infragistics XamGrid.

Add this  ViewModel  as a static resource in these two pages and add it as s DataContext for
each page layout and bind the ItemsSource of the both grids to the Products property of the ViewModel.

 There is a XAML code for the page with DataGrid:

.....

<UserControl.Resources>

<my:DataViewModel x:Key="dateViewModel" x:FieldModifier="public" />

</UserControl.Resources>

<Grid x:Name="LayoutRoot" Background="LightSteelBlue" DataContext="{StaticResource dateViewModel}">

<Grid.RowDefinitions>

    <RowDefinition Height="30" />

    <RowDefinition />           

</Grid.RowDefinitions>

<StackPanel Orientation="Horizontal">

    <Button x:Name="BtnMsGridLoadData" Style="{StaticResource PageButtonStyle}"

                               Content="Load Data" Command="{Binding Load}" />

    <Button x:Name="BtnMsGridPrint" Style="{StaticResource PageButtonStyle}"

                               Content="Generate Report" Command="{Binding PrintReport}"

                               CommandParameter="{Binding Products}"/>

    <TextBlock x:Name="PageNameTextBlock" Style="{StaticResource ApplicationNameStyle}"

                       Text="MS Grid Reporting"/>           

</StackPanel>

<sdk:DataGrid AutoGenerateColumns="False" Grid.Row="1" HorizontalAlignment="Stretch" Margin="20" Name="dataGrid" VerticalAlignment="Stretch"

               ItemsSource="{Binding Products}">

    <sdk:DataGrid.Columns>

        <sdk:DataGridTextColumn Header="SKU" Binding="{Binding SKU}" />

        <sdk:DataGridTextColumn Header="Name" Binding="{Binding Name}" />

        <sdk:DataGridTextColumn Header="Category" Binding="{Binding Category}" />

        <sdk:DataGridTextColumn Header="Supplier" Binding="{Binding Supplier}" />

        <sdk:DataGridTextColumn Header="UnitPrice" Binding="{Binding UnitPrice}" />

        <sdk:DataGridTextColumn Header="UnitsInStock" Binding="{Binding UnitsInStock}" />

        <sdk:DataGridCheckBoxColumn Header="OnBackOrder" Binding="{Binding OnBackOrder}" />

        <sdk:DataGridTextColumn Header="QuantityPerUnit" Binding="{Binding QuantityPerUnit}" />

    </sdk:DataGrid.Columns>

 

</sdk:DataGrid>

 

</Grid>


...

 

There is the similar code for the page with the Infragistics XamGrid:

... 

<UserControl.Resources>

    <my:DataViewModel x:Key="dateViewModel2" x:FieldModifier="public" />

</UserControl.Resources>

<Grid x:Name="LayoutRoot" Background="LightSkyBlue" DataContext="{StaticResource dateViewModel2}">

    <Grid.RowDefinitions>

        <RowDefinition Height="30" />

        <RowDefinition />

    </Grid.RowDefinitions>

    <StackPanel Orientation="Horizontal">

        <Button x:Name="BtnMsGridLoadData" Style="{StaticResource PageButtonStyle}"

                                   Content="Load Data" Command="{Binding Load}" />

        <Button x:Name="BtnMsGridPrint" Style="{StaticResource PageButtonStyle}"

                                   Content="Generate Report" Command="{Binding PrintReport}"

                                   CommandParameter="{Binding Products}" />

        <TextBlock x:Name="PageNameTextBlock" Style="{StaticResource ApplicationNameStyle}"

                           Text="XamGrid Reporting"/>           

    </StackPanel>

    <ig:XamGrid Grid.Row="1" HorizontalAlignment="Stretch" Margin="20" Name="xamGrid" VerticalAlignment="Stretch"

               ItemsSource="{Binding Products}" AutoGenerateColumns="False">

        <ig:XamGrid.Columns>

            <ig:TextColumn Key="SKU"/>

            <ig:TextColumn Key="Name"/>

            <ig:TextColumn Key="Category"/>

            <ig:TextColumn Key="Supplier"/>

            <ig:TextColumn Key="UnitPrice"/>

            <ig:TextColumn Key="UnitsInStock"/>

            <ig:CheckBoxColumn Key="OnBackOrder"/>

            <ig:TextColumn Key="QuantityPerUnit"/>

        </ig:XamGrid.Columns>

    </ig:XamGrid>

</Grid>

..... 

Build and run the application:

 Navigation application with an empty DataGrid before data is loaded.

 DataGrid with loaded data

 Print dialog. XPS Document Writer is selected.

XPS document is created. Report contains specific page headers, footers, items data visualizations
and summary parts. Report template contains all report settings.

Infragistics XamGrid works with Silverlight Reporting in the same way like DataGrid.

 There are features that are not yet implemented in the Silverlight Reporting like:

  • Subreports and grouping.
  • Support for images. Because the pages are not part of a real visual tree until printing time, Images do not work.
  • Print preview.
  • Report Progress Reporting.

Despite that the Silverlight Reporting is a good sample how to create a reports in Silverlight 4.