Thursday 14 June 2012

Building Silverlight Business Application with Telerik Report Viewer, step by step



Telerik Reports are Class based, so we will create a Class Library Project to contain Telerik Reports.

To feed the Telerik Reports with data using Entity Data Model, we cannot use the EDM located in the Silverlight Web project; as a circular dependency issue will rise once we add a reference to the Silverlight Web project from the Telerik Report Class Library project. As an alternative, we can create a new Class Library Project to contain the Entity Data Model that will feed the Telerik Report Class Library project.

To display the Telerik reports in Silverlight project we need to add few assemblies that will enable us to add the Telerik Report Viewer to the views.

Telerik Report Viewer need to see the Telerik Reports located in the Class Library Project, to enable this, Telerik provided the solution by adding a web service on the Silverlight Web project.

So, our Solution will hold four projects:
  1. Silverlight project (SLBAReport), 
  2. Silverlight Web project (SLBAReport.Web),
  3. Class Library to hold the Telerik Reports (ReportLibrary) and
  4. Class Library to hold the Entity Data Model to feed the Telerik Report Project (DBLibrary).
Fire up Microsoft Visual Studio 2010 and create a new Solution (SLTelerikReport) containing Silverlight Business Application (SLBAReport).

Adding the Telerik Report Project to the solution


  1. Right click the Solution, add new project.
  2. Choose Class Library (ReportLibrary). 
  3. Delete the default class Class1.cs


Adding the Database Project to the solution


  1. Right click the Solution, add new project. 
  2. Choose Class Library (DBLibrary). 
  3. Delete the default class Class1.cs
Now, our solution contain the four projects we need.

Adding References


ReportLibrary project

Right click References under ReportLibrary project, and choose Add Reference…

  1. Choose: Projects tab, and choose: DBLibrary
  2. Choose: .NET tab, and choose: System.Data.Entity


SLBAReport project

Right click References under SLBAReport project, and choose Add Reference…
Choose: .NET tab, and choose the following:

  1. System.Windows.Controls (it is added by default)
  2. Telerik.Windows.Controls
  3. Telerik.Windows.Controls.Input
  4. Telerik.Windows.Controls.Navigation
  5. Choose: Browse tab, and locate
  6. Telerik.ReportViewer.Silverlight.dll
  7. In the Telerik Examples folder: C:\Program Files (x86)\Telerik\Reporting Q1 2012\Examples\CSharp\SilverlightDemo\bin\


SLBAReport.Web project

Right click References under SLBAReport.Web project, and choose Add Reference…

  1. Choose: Projects tab, and choose DBLibrary
  2. Choose: Projects tab, and choose ReportLibrary



Building the Projects


DBLibrary Project


  1. Right click the project and choose, Add, Choose New Item
  2. Choose: ADO.NET Entity Model (DBModel), press Next
  3. Choose: Generate from database, press Next
  4. Choose or create your database connection and save it (DBEntities)
  5. Select the Tables, Views you want to include in your Entity Data Model
  6. Save and Complete the Wizard

P.S: I added a Table (CountryLookup) that contains (CountryID, and CountryName).

Open the App.Config in the DBLibrary Project and copy the DBEntities Connection String tag.
My connection string tag:

<add name="DBEntities" connectionString="metadata=res://*/DBModel.csdl|res://*/DBModel.ssdl|res://*/DBModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.\SQLEXPRESS;Initial Catalog=TestDB;Integrated Security=True;MultipleActiveResultSets=True;Application Name=EntityFramework&quot;" providerName="System.Data.EntityClient" />


In the SLBAReport.Web Project, open the Web.Config and paste the copied connection string within the ConnectionStrings tag.
The reason behind doing the last step is: at run time, only the startup Project connection strings are respected, so we need to copy our connection string from the DBLibrary Project to the Startup SLBAReport.Web Project.


Report Library Project

  1. Right click the project and choose, Add, Choose New Item
  2. Choose Telerik Report Q1 2012, or whatever version you have.
  3. Let’s leave the default name Report1.cs and choose Add.
  4. The Telerik Report Wizard will pop up, press Cancel.
  5. The Report designer is displayed with three main sections: Header, Details and Footer.
  6. From the Toolbox, drag a TextBox into the Details section.
  7. Double Click the Textbox, type =Fields.CountryName
  8. Right click on the Report and choose View Code, this will open up the Code behind file.
  9. Define an instance of the DBEntities (_context)
  10. Initialize the _context and set the Report Data Source.

    public partial class Report1 : Telerik.Reporting.Report
    {
        private DBEntities _context = null;
        public Report1()
        {
            InitializeComponent();
            _context = new DBEntities();
            this.DataSource = _context.CountryLookup;
        }
    }

Notice that both DBLibrary and ReportLibrary projects are running on the server, so we can communicate with the Entity Data Model directly.  Unlike the Client projects that require the Domain Service.


SLBAReport.Web Project


  1. Right click the Services folder in the SLBAReport.Web project, choose: Add, New Item.
  2. Choose: TextFile (ReportService.svc).
  3. Copy and paste the following line in the ReportService.svc file.

<%@ServiceHost Service="Telerik.Reporting.Service.ReportService, Telerik.Reporting.Service, Version=6.0.12.215, Culture=neutral, PublicKeyToken=A9D7983DFCC261BE" %>

Notice that Version=6.0.12.215 is the version of the used Telerik Report Viewer we are using.
You can find out which version you are using by following:

In SLBAReport Project, 
  1. Expand the Reference folder
  2. Right click on Telerik.ReportViewer.Silverlight, Remember we have added this reference earlier.
  3. Choose Properties and find Version.
  4. Double click the Web.Config, reach to the bottom.
  5. We will replace the following System.ServiceModel tag
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>

With the following :

<system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
        <services>
            <service
                    name="Telerik.Reporting.Service.ReportService"
                    behaviorConfiguration="ReportServiceBehavior">
             <endpoint
                    address=""
                    binding="basicHttpBinding"
                    contract="Telerik.Reporting.Service.IReportService">
                    <identity>
                        <dns value="localhost" />
                    </identity>
                </endpoint>
                <endpoint
                        address="resources"
                        binding="webHttpBinding"
                        behaviorConfiguration="WebBehavior"
                        contract="Telerik.Reporting.Service.IResourceService"/>
                <endpoint
                        address="mex"
                        binding="mexHttpBinding"
                        contract="IMetadataExchange" />
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ReportServiceBehavior">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
            <endpointBehaviors>
                <behavior name="WebBehavior">
                    <webHttp />
                </behavior>
            </endpointBehaviors>
        </behaviors>
    </system.serviceModel>


SLBAReport Project


  1. Expand the Views folder in the SLBAReport project, and double click the Home.xaml
  2. From the Toolbox, drag ReportViewer on the Home.xaml (reportViewer1)
  3. Adjust the width and height by dragging the control edges.
  4. We need to set couple of properties for the Report Viewer control: Report and ReportServiceUri.


We can do this either in XAML:

<telerik:ReportViewer Name="reportViewer1" ReportServerUri="../Services/ReportService.svc"
 Report="ReportLibrary.Report1, ReportLibrary"/>

Or in Code behind:
  1. Right click Home.xaml, choose View Code.
  2. In the OnNavigatedTo event paste the following:
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            this.reportViewer1.ReportServiceUri = new Uri("../Services/ReportService.svc", UriKind.RelativeOrAbsolute);
            this.reportViewer1.Report = "ReportLibrary.Report1, ReportLibrary";
        }



Save All, Clean, Rebuild and Run

Tuesday 24 January 2012

Async Validation - Calling server validation from client async

Lets define our example:
  • Project name: SLUsers
  • Domain Service: Domain
  • Entity: Users
    •  UserID
    •  UserName
    •  Password
We need to check the availability of the UserName while the person is still filling the registration form.


Step #1
======
In the Server App, in your Domain Service create the following :

        [Invoke]
        public bool IsUserNameUsed(string userName)
        {
            Users _users = null;
            using (DomainEntities domainEntity = new DomainEntities())
            {
                _users = domainEntity.Users.Where(s => s.UserName == userName).SingleOrDefault();
            }
            return _users != null;
        }
 
 
Step #2
======
In the Silverlight Client App, in the Model folder add new class and paste the following:
 
 
using System.ComponentModel.DataAnnotations;
using SLUsers.Web.Services;
using System.ServiceModel.DomainServices.Client;
namespace SLUsers.Web.Models
{
    public partial class Users
    {
        private ValidationResult _isUserNameUsedValidationResult;
        partial void OnUserNameChanged()
        {
           if (IsDeserializing)
               return;
            DomainContext context = new DomainContext();
            context.IsUserNameUsed(UserName,
                delegate(InvokeOperation<bool> isUsed)
                {
                    if (isUsed.Value)
                    {
                        _isUserNameUsedValidationResult = new ValidationResult("Username already exist in database !", new string[] {"UserName"});
                         
                        ValidationErrors.Add(_isUserNameUsedValidationResult);
                    }
                }, null);
        }
         
    }
}
 
 
 
Notes
=====
1- The class created in Step #2 in the client application have the name space SLUsers.Web.Models which referes to the Web.
2- The part which triggers the validation lies in the step #2 in the line:
 _isUserNameUsedValidationResult = new ValidationResult("Username already exist in database !", new string[] {"UserName"});
 where the field name to be validated is 'Username' as:
  new string[] {"UserName"}.
3- In XAML page binding, don't forget to raise the validation error as:
 Text="{Binding Path=UserName, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"
Have a nice day :)
 

Monday 5 December 2011

Printing in WPF

The most stright forward tool for printing in WPF was written my Chris L Mullin http://www.codeproject.com/KB/WPF/CustomDataGridDocPag.aspx.
It is only one class that you add to your project, and start printing.
I have added two little things to the class:

- RightToLeft support to the class; and that made life much easier for me.
- Customize the Styles (added by Chris) using XAML code in the App.xaml.
But, full credit goes to Chris.

The whole idea is to add a DataGrid and customize the column widths, headers, ...etc. once done, add a button and paste few lines of code, that will create a paginator from the DataGrid and stright to the printer.

Steps to achieve our WPF printing goal:

Step #1
======

Add new Class to your WPF project and past the following:
///////////////////////////CodeStartHere//////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Documents;
using System.Windows.Controls;
using System.Windows;
using System.Collections.ObjectModel;
using System.Collections;
using System.Windows.Markup;
using System.Windows.Data;
using System.ComponentModel;
namespace WPFPrintingExample
{
    public class CustomDataGridDocumentPaginator : DocumentPaginator
    {
        #region Private Members
        private DataGrid _documentSource;
        private Collection<ColumnDefinition> _tableColumnDefinitions;
        private double _avgRowHeight;
        private double _availableHeight;
        private int _rowsPerPage;
        private int _pageCount;
        #endregion
        #region Constructor
        public CustomDataGridDocumentPaginator(DataGrid documentSource, string documentTitle, Size pageSize, Thickness pageMargin)
        {
            _tableColumnDefinitions = new Collection<ColumnDefinition>();           
            _documentSource = documentSource;
            this.DocumentTitle = documentTitle;
            this.PageSize = pageSize;
            this.PageMargin = pageMargin;
            if (_documentSource != null)
                MeasureElements();
            this.PageDirection = documentSource.FlowDirection;
            ReadStyles();
        }
        private void ReadStyles()
        {
            this.DocumentHeaderTextStyle = (Style)App.Current.Resources["DocumentHeaderTextStyle"];
            this.AlternatingRowBorderStyle = (Style)App.Current.Resources["AlternatingRowBorderStyle"];
            this.DocumentFooterTextStyle = (Style)App.Current.Resources["DocumentFooterTextStyle"];
            this.TableCellTextStyle = (Style)App.Current.Resources["TableCellTextStyle"];
            this.TableHeaderTextStyle = (Style)App.Current.Resources["TableHeaderTextStyle"];
            this.TableHeaderBorderStyle = (Style)App.Current.Resources["TableHeaderBorderStyle"];
            this.GridContainerStyle = (Style)App.Current.Resources["GridContainerStyle"];
        }

        #endregion
        #region Public Properties
        #region Styling
        public Style AlternatingRowBorderStyle { get; set; }
        public Style DocumentHeaderTextStyle { get; set; }
        public Style DocumentFooterTextStyle { get; set; }
        public Style TableCellTextStyle { get; set; }
        public Style TableHeaderTextStyle { get; set; }
        public Style TableHeaderBorderStyle { get; set; }
        public Style GridContainerStyle { get; set; }
        #endregion
        public FlowDirection PageDirection { get; set; }
       
        public string DocumentTitle { get; set; }
        public Thickness PageMargin { get; set; }
        public override Size PageSize { get; set; }
        public override bool IsPageCountValid
        {
            get { return true; }
        }
        public override int PageCount
        {
            get { return _pageCount; }
        }
        public override IDocumentPaginatorSource Source
        {
            get { return null; }
        }
        #endregion
        #region Public Methods
        public override DocumentPage GetPage(int pageNumber)
        {
            DocumentPage page = null;
            List<object> itemsSource = new List<object>();
            ICollectionView viewSource = CollectionViewSource.GetDefaultView(_documentSource.ItemsSource);
            if (viewSource != null)
            {
                foreach (object item in viewSource)
                    itemsSource.Add(item);
            }
            if (itemsSource != null)
            {
                int rowIndex = 1;
                int startPos = pageNumber * _rowsPerPage;
                int endPos = startPos + _rowsPerPage;
                //Create a new grid
                Grid tableGrid = CreateTable(true) as Grid;
                for (int index = startPos; index < endPos && index < itemsSource.Count; index++)
                {
                    Console.WriteLine("Adding: " + index);
                    if (rowIndex > 0)
                    {
                        object item = itemsSource[index];
                        int columnIndex = 0;
                        if (_documentSource.Columns != null)
                        {
                            foreach (DataGridColumn column in _documentSource.Columns)
                            {
                                if (column.Visibility == Visibility.Visible)
                                {
                                    AddTableCell(tableGrid, column, item, columnIndex, rowIndex);
                                    columnIndex++;
                                }
                            }
                        }
                        if (this.AlternatingRowBorderStyle != null && rowIndex % 2 == 0)
                        {
                            Border alernatingRowBorder = new Border();
                            alernatingRowBorder.Style = this.AlternatingRowBorderStyle;
                            alernatingRowBorder.SetValue(Grid.RowProperty, rowIndex);
                            alernatingRowBorder.SetValue(Grid.ColumnSpanProperty, columnIndex);
                            alernatingRowBorder.SetValue(Grid.ZIndexProperty, -1);
                            tableGrid.Children.Add(alernatingRowBorder);
                        }
                    }
                    rowIndex++;
                }
               
                tableGrid.FlowDirection = PageDirection;
                page = ConstructPage(tableGrid, pageNumber);
            }
            return page;
        }
        #endregion
        #region Private Methods
        /// <summary>
        /// This function measures the heights of the page header, page footer and grid header and the first row in the grid
        /// in order to work out how manage pages might be required.
        /// </summary>
        private void MeasureElements()
        {
            double allocatedSpace = 0;
            //Measure the page header
            ContentControl pageHeader = new ContentControl();
            pageHeader.Content = CreateDocumentHeader();
            allocatedSpace = MeasureHeight(pageHeader);
           
            //Measure the page footer
            ContentControl pageFooter = new ContentControl();
            pageFooter.Content = CreateDocumentFooter(0);
            allocatedSpace += MeasureHeight(pageFooter);
           
            //Measure the table header
            ContentControl tableHeader = new ContentControl();
            tableHeader.Content = CreateTable(false);
            allocatedSpace += MeasureHeight(tableHeader);
            //Include any margins
            allocatedSpace += this.PageMargin.Bottom + this.PageMargin.Top;
            //Work out how much space we need to display the grid
            _availableHeight = this.PageSize.Height - allocatedSpace;
            //Calculate the height of the first row
            _avgRowHeight = MeasureHeight(CreateTempRow());
            //Calculate how many rows we can fit on each page
            double rowsPerPage = Math.Floor(_availableHeight / _avgRowHeight);
            if (!double.IsInfinity(rowsPerPage))
                _rowsPerPage = Convert.ToInt32(rowsPerPage);
            //Count the rows in the document source
            double rowCount = CountRows(_documentSource.ItemsSource);
            //Calculate the nuber of pages that we will need
            if (rowCount > 0)
                _pageCount = Convert.ToInt32(Math.Ceiling(rowCount / rowsPerPage));
        }
        /// <summary>
        /// This method constructs the document page (visual) to print
        /// </summary>
        private DocumentPage ConstructPage(Grid content, int pageNumber)
        {
            if (content == null)
                return null;
            //Build the page inc header and footer
            Grid pageGrid = new Grid();
            //Header row
            AddGridRow(pageGrid, GridLength.Auto);
            //Content row
            AddGridRow(pageGrid, new GridLength(1.0d, GridUnitType.Star));
            //Footer row
            AddGridRow(pageGrid, GridLength.Auto);
            ContentControl pageHeader = new ContentControl();
            pageHeader.Content = this.CreateDocumentHeader();
            pageGrid.Children.Add(pageHeader);
           
            if (content != null)
            {
                content.SetValue(Grid.RowProperty, 1);
                pageGrid.Children.Add(content);
            }
            ContentControl pageFooter = new ContentControl();
            pageFooter.Content = CreateDocumentFooter(pageNumber + 1);
            pageFooter.SetValue(Grid.RowProperty, 2);
            pageFooter.FlowDirection = PageDirection;
            pageGrid.Children.Add(pageFooter);
            double width = this.PageSize.Width - (this.PageMargin.Left + this.PageMargin.Right);
            double height = this.PageSize.Height - (this.PageMargin.Top + this.PageMargin.Bottom);
            pageGrid.Measure(new Size(width, height));
            pageGrid.Arrange(new Rect(this.PageMargin.Left, this.PageMargin.Top, width, height));
            return new DocumentPage(pageGrid);
        }
        /// <summary>
        /// Creates a default header for the document; containing the doc title
        /// </summary>
        private object CreateDocumentHeader()
        {
            Border headerBorder = new Border();
            TextBlock titleText = new TextBlock();
            titleText.Style = this.DocumentHeaderTextStyle;
            titleText.TextTrimming = TextTrimming.CharacterEllipsis;
            titleText.Text = this.DocumentTitle;
            titleText.HorizontalAlignment = HorizontalAlignment.Center;
            titleText.TextAlignment = TextAlignment.Center;
            headerBorder.Child = titleText;
            headerBorder.FlowDirection = PageDirection;
            return headerBorder;
        }
        /// <summary>
        /// Creates a default page footer consisting of datetime and page number
        /// </summary>
        private object CreateDocumentFooter(int pageNumber)
        {
            Grid footerGrid = new Grid();
            footerGrid.Margin = new Thickness(0, 10, 0, 0);
            ColumnDefinition colDefinition = new ColumnDefinition();
            colDefinition.Width = new GridLength(0.5d, GridUnitType.Star);
            TextBlock dateTimeText = new TextBlock();
            dateTimeText.Style = this.DocumentFooterTextStyle;
            switch (PageDirection)
            {
                case FlowDirection.LeftToRight:
                   dateTimeText.Text = DateTime.Now.ToString("dd-MMM-yyy HH:mm");
                   break;
                case FlowDirection.RightToLeft:
                   dateTimeText.Text = DateTime.Now.ToString("yyyy-MM-dd");
                   break;
            }
            dateTimeText.FlowDirection = PageDirection;
            footerGrid.Children.Add(dateTimeText);
            TextBlock pageNumberText = new TextBlock();
            pageNumberText.Style = this.DocumentFooterTextStyle;
            switch (PageDirection)
            {
                case FlowDirection.LeftToRight:
                    pageNumberText.Text = string.Format("Page {0} of {1}",pageNumber.ToString(),this.PageCount.ToString());
                    break;
                case FlowDirection.RightToLeft:
                    pageNumberText.Text = string.Format("صفحة {0} من {1}", pageNumber.ToString(), this.PageCount.ToString());                   
                    break;
            }
            pageNumberText.HorizontalAlignment = HorizontalAlignment.Right;
            pageNumberText.FlowDirection = PageDirection;
            pageNumberText.SetValue(Grid.ColumnProperty, 1);
            footerGrid.Children.Add(pageNumberText);
            return footerGrid;
        }
        /// <summary>
        /// Counts the number of rows in the document source
        /// </summary>
        /// <param name="itemsSource"></param>
        /// <returns></returns>
        private double CountRows(IEnumerable itemsSource)
        {
            int count = 0;
            if (itemsSource != null)
            {
                foreach (object item in itemsSource)
                    count++;
            }
            return count;
        }
        /// <summary>
        /// The following function creates a temp table with a single row so that it can be measured and used to
        /// calculate the totla number of req'd pages
        /// </summary>
        /// <returns></returns>
        private Grid CreateTempRow()
        {
            Grid tableRow = new Grid();
            if (_documentSource != null)
            {
                foreach (ColumnDefinition colDefinition in _tableColumnDefinitions)
                {
                    ColumnDefinition copy = XamlReader.Parse(XamlWriter.Save(colDefinition)) as ColumnDefinition;
                    tableRow.ColumnDefinitions.Add(copy);
                }
                foreach (object item in _documentSource.ItemsSource)
                {
                    int columnIndex = 0;
                    if (_documentSource.Columns != null)
                    {
                        foreach (DataGridColumn column in _documentSource.Columns)
                        {
                            if (column.Visibility == Visibility.Visible)
                            {
                                AddTableCell(tableRow, column, item, columnIndex, 0);
                                columnIndex++;
                            }
                        }
                    }
                    //We only want to measure teh first row
                    break;
                }
            }
            return tableRow;
        }
        /// <summary>
        /// This function counts the number of rows in the document
        /// </summary>
        private object CreateTable(bool createRowDefinitions)
        {
            if (_documentSource == null)
                return null;
            Grid table = new Grid();
            table.Style = this.GridContainerStyle;
            int columnIndex = 0;

            if (_documentSource.Columns != null)
            {
                double  totalColumnWidth = _documentSource.Columns.Sum(column => column.Visibility == Visibility.Visible ? column.Width.Value : 0);
                foreach (DataGridColumn column in _documentSource.Columns)
                {
                    if (column.Visibility == Visibility.Visible)
                    {
                        AddTableColumn(table, totalColumnWidth, columnIndex, column);
                        columnIndex++;
                    }
                }
            }
            if (this.TableHeaderBorderStyle != null)
            {
                Border headerBackground = new Border();
                headerBackground.Style = this.TableHeaderBorderStyle;
                headerBackground.SetValue(Grid.ColumnSpanProperty, columnIndex);
                headerBackground.SetValue(Grid.ZIndexProperty, -1);
                table.Children.Add(headerBackground);
            }
            if (createRowDefinitions)
            {
                for (int i = 0; i <= _rowsPerPage; i++)
                    table.RowDefinitions.Add(new RowDefinition());
            }
            return table;
        }
        /// <summary>
        /// Measures the height of an element
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private double MeasureHeight(FrameworkElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");
            element.Measure(this.PageSize);
            return element.DesiredSize.Height;
        }
        /// <summary>
        /// Adds a column to a grid
        /// </summary>
        /// <param name="grid">Grid to add the column to</param>
        /// <param name="columnIndex">Index of the column</param>
        /// <param name="column">Source column defintition which will be used to calculate the width of the column</param>
        private void AddTableColumn(Grid grid, double totalColumnWidth, int columnIndex, DataGridColumn column)
        {
            double proportion = column.Width.Value / (this.PageSize.Width - (this.PageMargin.Left + this.PageMargin.Right));
            ColumnDefinition colDefinition = new ColumnDefinition();
            colDefinition.Width = new GridLength(proportion, GridUnitType.Star);
            grid.ColumnDefinitions.Add(colDefinition);
            TextBlock text = new TextBlock();
            text.Style = this.TableHeaderTextStyle;
            text.TextTrimming = TextTrimming.CharacterEllipsis;
            text.Text = column.Header.ToString();
            text.SetValue(Grid.ColumnProperty, columnIndex);
            grid.Children.Add(text);
            _tableColumnDefinitions.Add(colDefinition);
        }
        /// <summary>
        /// Adds a cell to a grid
        /// </summary>
        /// <param name="grid">Grid to add teh cell to</param>
        /// <param name="column">Source column definition which contains binding info</param>
        /// <param name="item">The binding source</param>
        /// <param name="columnIndex">Column index</param>
        /// <param name="rowIndex">Row index</param>
        private void AddTableCell(Grid grid, DataGridColumn column, object item, int columnIndex, int rowIndex)
        {           
            if (column is DataGridTemplateColumn)
            {
                DataGridTemplateColumn templateColumn = column as DataGridTemplateColumn;
                ContentControl contentControl = new ContentControl();
                contentControl.Focusable = true;
                contentControl.ContentTemplate = templateColumn.CellTemplate;
                contentControl.Content = item;
                contentControl.SetValue(Grid.ColumnProperty, columnIndex);
                contentControl.SetValue(Grid.RowProperty, rowIndex);
                grid.Children.Add(contentControl);
            }
            else if (column is DataGridTextColumn)
            {
                DataGridTextColumn textColumn = column as DataGridTextColumn;
                TextBlock text = new TextBlock { Text = "Text" };
                text.Style = this.TableCellTextStyle;
                text.TextTrimming = TextTrimming.CharacterEllipsis;
                text.DataContext = item;
                Binding binding = textColumn.Binding as Binding;
                //if (!string.IsNullOrEmpty(column.DisplayFormat))
                    //binding.StringFormat = column.DisplayFormat;
                text.SetBinding(TextBlock.TextProperty, binding);
                text.SetValue(Grid.ColumnProperty, columnIndex);
                text.SetValue(Grid.RowProperty, rowIndex);
                grid.Children.Add(text);
            }
        }
        /// <summary>
        /// Adds a row to a grid
        /// </summary>
        private void AddGridRow(Grid grid, GridLength rowHeight)
        {
            if (grid == null)
                return;
            RowDefinition rowDef = new RowDefinition();
            if (rowHeight != null)
                rowDef.Height = rowHeight;
            grid.RowDefinitions.Add(rowDef);
        }
        #endregion
    }
}
///////////////////////////CodeEndsHere//////////////////////
Do not forget to change the namespace to your project name, mine was: WPFPrintingExample
Step #2
======
Open the App.xaml found in your WPF project and paste the following between the  <Application.Resources> and  </Application.Resources> tags.
<!--XAML Code Starts Here-->
        <Style x:Key="DocumentHeaderTextStyle" TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="FontFamily" Value="Aria"/>
            <Setter Property="FontSize" Value="30"/>
        </Style>
        <Style x:Key="AlternatingRowBorderStyle" TargetType="Border">
            <Setter Property="BorderBrush" Value="#FFFFFFFF"/>
            <Setter Property="Background" Value="#FFFFFFFF" />
        </Style>
       
        <Style x:Key="DocumentFooterTextStyle" TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Foreground" Value="Black"/>
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="12"/>
        </Style>
        <Style x:Key="TableCellTextStyle" TargetType="TextBlock">
            <Setter Property="Padding" Value="5,0,0,0" />
        </Style>
        <Style x:Key="TableHeaderTextStyle" TargetType="TextBlock">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Foreground" Value="White"/>
            <Setter Property="Background" Value="Black" />
            <Setter Property="FontFamily" Value="Times New Roman"/>
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="TextAlignment" Value="Center" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style x:Key="TableHeaderBorderStyle" TargetType="Border">
            <Setter Property="BorderBrush" Value="Red"/>
            <Setter Property="Background" Value="Red" />
        </Style>
        <Style x:Key="GridContainerStyle" TargetType="Grid">
            <Setter Property="Background" Value="#FFFCFCFC"/>
        </Style>
<!--XAML Code Ends Here-->
This XAML code is an easy way to customize your WPF report.
Step #3
======
Add a DataGrid name it dgPringData to a new WPF window and fill it with data.
Add new button name it btnPrint and add the fllowing code to the click event:
///////////////////////////CodeStartHere//////////////////////
         private void btnPrint_Click(object sender, RoutedEventArgs e)
        {
            PrintDialog printDialog = new PrintDialog();
            if (printDialog.ShowDialog() == false)
                return;
            string documentTitle = "WPF Report Title";
            Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
            CustomDataGridDocumentPaginator paginator = new CustomDataGridDocumentPaginator(dgPringData as DataGrid, documentTitle, pageSize, new Thickness(30, 20, 30, 20));
            printDialog.PrintDocument(paginator, "Grid");
        }
///////////////////////////CodeEndsHere//////////////////////

Source: Chris L Mullin

Wednesday 30 November 2011

Printing in Silverlight

Example:
To make the code listed in this post easier, I am providing the name of the project and an entity of which I will be printing my report from its data.
Project name: SLPrinting
Entity name: Customer (ID, FirstName, LastName, CompanyName)

step 1
=====
Declare IEnumerable of type our entity and fill it with data.
IEnumerable<Customer> _customersToPrint
Create 'PrintDocument' object and implement its printing events by pasting the following in the PrintButton click event:
        private int _printedItems;
        private IEnumerable<Customer> _customersToPrint;
        private void PrintButton_Click(object sender, RoutedEventArgs e)
        {
            var printDoc = new PrintDocument();
            printDoc.BeginPrint += BeginPrint;
            printDoc.PrintPage += PrintPage;
            printDoc.EndPrint += EndPrint;
            printDoc.Print("CustomersReport");  //Document name in the print queue.
            //this.DialogResult = true;
        }
        private void BeginPrint(object sender, BeginPrintEventArgs e)
        {
            _printedItems = 0;
        }
        private void PrintPage(object sender, PrintPageEventArgs e)
        {
            var _myReport = new myReport();
            e.PageVisual = _myReport;
            _myReport.PreparePage(
                _customersToPrint,
                _printedItems,
                e.PrintableArea
                );
            _printedItems += _myReport.NumberOfCustomers;
            e.HasMorePages = _printedItems < _customersToPrint.Count();
        }
        private void EndPrint(object sener, EndPrintEventArgs e)
        {
            //DialogResult = true;
        }


step 2
=====
In Silverlight project Add new item, choose 'Silverlight UserControl' and name it: 'myReport.xaml'
We will use this added item to design our report using a DataTemplate.
paste the following:
<UserControl x:Class="SLPrintingExample.myReport"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <DataTemplate x:Key="dtCustomersTemplate">
            <StackPanel Margin="10">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Path=FirstName}" Width="200"/>
                    <TextBlock Text="{Binding Path=LastName}" Width="200"/>
                    <TextBlock Text="{Binding Path=CompanyName}"/>
                </StackPanel>
                <Rectangle Fill="Black" Height="2" />
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <StackPanel x:Name="spHeader" Grid.Row="0">
            <TextBlock HorizontalAlignment="Center"
                   TextWrapping="Wrap"
                   Text="My Silverlight Report"
                   VerticalAlignment="Center"
                   FontWeight="Bold" />
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="First name" Width="200" />
                <TextBlock Text="Last name" Width="200"/>
                <TextBlock Text="Company name" />
            </StackPanel>
        </StackPanel>
       
        <TextBlock HorizontalAlignment="Center"
                   TextWrapping="Wrap"
                   Text="Place holder"
                   x:Name="txbReportFooter"
                   VerticalAlignment="Center"
                   Grid.Row="2"/>
        <StackPanel Grid.Row="1"
                    x:Name="spCustomersPanel">
        </StackPanel>
    </Grid>
</UserControl>
The UserControl contains the following:
 Description UI x:Name
==================================
  - DataTemplate:  dtCustomersTemplate (How to display each row)
  - Report header:  spReportHeader 
  - Report footer: txbReportFooter 
  - Report details: spCustomersPanel  (Holds the data rows)

step 3
=====
In myReport.xaml.cs paste the following:
        public int NumberOfCustomers { get; private set; }
        public void PreparePage(IEnumerable<Customer> customers, int startIndex, Size pageSize)
        {
            spCustomersPanel.Children.Clear();
            UpdateLayout();
            foreach (Customer custom in customers.Skip(startIndex))
            {
                var content = new ContentPresenter
                {
                    Content = custom,
                    ContentTemplate = Resources["dtCustomersTemplate"] as DataTemplate
                };
                spCustomersPanel.Children.Add(content);
                UpdateLayout();
                if (DesiredSize.Height >= pageSize.Height)
                {
                    spCustomersPanel.Children.Remove(content);
                    break;
                }
            }
            NumberOfCustomers = spCustomersPanel.Children.Count;
            txbReportFooter.Text = string.Format("Customers {0} to {1} ", startIndex + 1, startIndex + NumberOfCustomers);
            UpdateLayout();
        }

Saturday 12 November 2011

Display Child data with Parent from different tables in Silverlight Datagrid

Let us describe the problem, we have two tables:

 ParentTable
  FatherID, FatherName

 ChildTable
  ChildID, ChildName, FatherId

Now in DomainService.cs the GetChildTable() query looks like:

        public IQueryable<ChildTable> GetChildTable()
        {
            return this.ObjectContext.ChildTable;
        }

and when we bind FatherName with ChildName in XAML we don't see FatherName as we expect.

            <sdk:DataGrid  x:Name="dgFamily" AutoGenerateColumns="False">
                <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn Header="Child name" Binding="{Binding Path=ChildName}" />
                <sdk:DataGridTextColumn Header="Father name" Binding="{Binding Path=FatherTable.FatherName}" />

This is due to Domain Service does not include the FatherTable with ChildTable in the GetChildTable() query, unless we ask for it.

We have to edit the DomainService.cs file and use the 'Include' in our query as:

        public IQueryable<ChildTable> GetChildTable()
        {
            return this.ObjectContext.ChildTable.Include("FatherTable");
        }

Also, if we look at the Metadata file: DomainService.metadata.cs it will look like:

        internal sealed class ChildTableMetadata
        {
            private ChildTableMetadata()
            {
            }
            public FatherTable FatherTable { get; set; }

We need to update and add the '[Include]' before the FatherTable public property, as:

        internal sealed class ChildTableMetadata
        {
            private ChildTableMetadata()
            {
            }
            [Include]
            public FatherTable FatherTable { get; set; }

Now, test you binding..
It is working

Tuesday 8 November 2011

Adding Page Transition Effect in Silverlight Application

In MSVS2010 Create a new Silverlight Navigation Project.
In Silverlight project, right click MainPage.xaml and select Open in Expression Blend.
In Objects and Timeline tab find (UserControl>LayoutRoot>ContentBorder>ContentFrame)
Right click ContentFrame > Edit Template > Edit a copy.
A Create ControlTemplate Resource box will appear.
Name the Key: CustomTransitioningNavFrame.
In Define In:  Choose Resource dictionary and press New... button.
New Item box will appear, name the new dictionary: CustomControls.xaml and press Ok.
Back to the earlier box, choose our new file CustomControls.xaml and press OK.
Delete the ContentPresenter control.
While Border selected add TransitioningContentPresenter control.
Modify XAML of the TCP control,
 Set Transition= DefaultTransition, Normal, UpTransition and DownTransition.
 Set Content={TemplateBinding Content}.
We are done with the minimum Transition effect. We can add our custom transition effect, but this is not covered in this post.

Thursday 3 November 2011

Make Silverlight application run out of the Browser

To make Silverlight application runs out of the Browser follow:
  • Right click Silverlight application > Properties > Silverlight (tab) > check Enable running application out of the browser.
  • Click on Out-of-Browser Settings... button if you like to change the application settings.
  • Right click Silverlight application > Set as StartUp Project.
  • Right click Silverlight application > Properties > Debug (tab) > Choose Out-of-browser application. [We will have to choose the Web application]