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]

Sunday 30 October 2011

Supporting Multilanguage in Silverlight Application

To build up a multilanguage Silverlight Application follow the step by step tutorial and you will be working in minutes :)
Start MSVS2010 > New Project > Silverlight Business Application.


Step #1 (Defining supported languages in the SL project)
=============================================
Right click Silverlight Client Application > Unload Project.
Right click Silverlight Client Application > Edit (SilverlightBusinessApplication.csProj).
Find tag named: <SupportedCultures></SupportedCultures>
Add languages that your application will support like
<SupportedCultures>ar-LY,en-US</SupportedCultures>
Save and close file.
Right click Silverlight Client Application > Reload Project.


Step #2 (Adding the resource file for different languages)
==============================================
In Silverlight Client Applciation > (Assets) folder > (Resources) folder
copy (ApplicationStrings.resx) and paste it in the same folder.
Rename the new file following the language convention (example: (ApplicationStrings.ar-LY.resx) for Arabic-Libyan).
Delete any cs file created for ApplicationStrings.ar-LY.resx as we don't need it.
Open the file (ApplicationStrings.ar-LY.resx) by double clicking it and edit the (value) field of each application string.
Add new field (Name: AppFlowDirection)
    Value: (LeftToRight) in ApplicationStrings.resx
    Value: (RightToLeft) in ApplicationStrings.ar-LY.resx

Save file and close.


Step #3 (Solving a bug in MSVS2010)
=============================
In Silverlight Client Applciation > (Assets) folder > (Resources) folder
Open file ApplicationStrings.Designer.cs which is the cs file of the ApplicationStrings.resx file and change the ApplicationStrings constructor  modifier
from inner to Public.
Save file and close.


Step #4 (Handling the change language event)
======================================
In Silverlight Client Applciation > (Assets) folder > (Resources) folder
Open file ApplicationResources.cs and
Implement the INotifyPropertyChanged interface for the ApplicationResources class.
Update the public properties so they look like:
        public ApplicationStrings Strings
        {
            get { return applicationStrings; }
            set { OnPropertyChanged("Strings"); }
        }
        public ErrorResources Errors
        {
            get { return errorResources; }
            set { OnPropertyChanged("Errors"); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (propertyName!=null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
Save file and close.


Step #5 (Adding UI to change the language)
====================================
In Silverlight Client Applciation Open MainPage.xaml and insert a comboBox after the about link; paste the following:
            <ComboBox Name="Language" SelectionChanged="Language_SelectionChanged" Width="80">
                <ComboBoxItem Content="English" Tag="en-US" IsSelected="True" />
                <ComboBoxItem Content="عربي" Tag="ar-LY" />
            </ComboBox>
Navigate to the new event Language_SelectionChanged and paste the following:
            Thread.CurrentThread.CurrentCulture = new CultureInfo(((ComboBoxItem) ((ComboBox)sender).SelectedItem).Tag.ToString());
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(((ComboBoxItem) ((ComboBox)sender).SelectedItem).Tag.ToString());
            ((ApplicationResources)App.Current.Resources["ApplicationResources"]).Strings = new ApplicationStrings();
Save file and close.


Step #6 (Supporting Bidirectional)
=========================
In the MainPage.xaml add the following to the main UserControl tag:
FlowDirection="{Binding Path=Strings.AppFlowDirection, Source={StaticResource ApplicationResources}}"
Save file and close.


Step #7 (Test the application)
======================
Change the languages from the comboBox and the app will respond.

Step by step building a Silverlight Business Application

I will present a stright forward step by step tutorial to build a complete Silverlight Business Application and how to merge the Membership provider database with our application database.

  1. Create your SQL Server db (example: dbExample).
  2. Run aspnet_regsql.exe found in: (C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe)
  3. Choose your SQL Server instance and your database (dbExample) and go.
  4. Check new tables add to your database (dbExample) by asnet_regsql.
  5. Run MSVS2010 > New Project > Silverlight Business Application.
  6. Add to Web Applicataiono ADO.net EDM.
  7. Connect to your database (dbExample).
  8. Choose your tables.
  9. Open file Web.Config in Web Application
  10. Copy ConnectionString for your database (dbExample) for the EDM and paste it in the ConnectionString named ApplicationService.
  11. Choose Web Application and Run ASP.NET Confiugration
  12. Test connection
  13. Add Roles (example: Admin).
  14. Add User (example: viewUser).
  15. Add User (example: AdminUser) member of Admin Role.
  16. Add DomainService to Web Application, choose your tables, name it (exampleDomainService).
  17. In your DomainService (exampleDomainService) before each IQuerable query add the following:
  18.  nothing    query can be accessed by any user.
  19.  [RequiresAuthentication]  query can be accessed by logged in users only.
  20.  [RequiresRole("Admin")]  query can be accessed by logged in users and member of Role: (Admin).
  21. In Client Application in (Views) folder add (Silverlight Page), name it examplePage.xaml.

Thursday 27 October 2011

Publishing Silverlight + WCF RIA Services Application on the web

My experience in publishing a silverlight WCF RIA services application in ISQSolutions hosting plan showed the following:

  1. Create new folder under the root folder www (example SLApp).
  2. Copy the Domain Service assemblies (3 files) to application bin folder after publish:
    • System.ServiceModel.DomainServices.EntityFramework.dll
    • System.ServiceModel.DomainServices.Hosting.dll
    • System.ServiceModel.DomainServices.Server.dll
  3. Upload application to created folder in the hosting plan (SLApp).
  4. From the hosting control panel, create application for the created folder (SLApp).
  5. Run you application.