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
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
how do you add landscape orientation support for your paginator?
ReplyDeleteThanks It Help Me Very Much
ReplyDeleteIs there a way to keep any grouping in the DataGrid?
ReplyDeleteVery useful. Slight mod to methods AddTableColumn and CreateTable, applies when datagrid's columns do not have a pre-defined width.
ReplyDeleteCalculate width using column.ActualWidth, instead of column.Width.Value
Thanks for the style samples! I've extended this class to support ListViews with GridViews and variable height rows.
ReplyDeletehttps://gist.github.com/matelich/0319046157d6792caa3b