Sunday, July 25, 2010

Silverlight 4.0 Datagrid printing

Recently I had a requirement to print data from Silverlight datagrid which i developed and integrated it in an asp.net project to serve as a dashboard for client administrative tasks. There is one feature that is not supported in Silverlight 4.0 and it's the ability to specify the print preference option in code. I needed this feature because the dadagrid is quite long and needs to be printed as landscape. Anyhow, the printing is straight forward, we just need to new up a silverlight user control to server as the header for each page being printed and we can also do the same if we need a footer. Below is the header user control i created. We need to add this to our silverlight project.

<UserControl x:Class="DB.Silverlight.Views.ApplicationPrintHeadView"
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="100" d:DesignWidth="422" Width="1100">
<Grid Margin="20,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="130" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="90" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition  ></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Name="h1" >First Name</TextBlock>
<TextBlock Grid.Column="1" Name="h2" >Last Name</TextBlock>
<TextBlock Grid.Column="2" Name="h3">Phone</TextBlock>
<TextBlock Grid.Column="3" Name="h4">Business Name</TextBlock>
<TextBlock Grid.Column="4" >Tier</TextBlock>
<TextBlock Grid.Column="5" >Status</TextBlock>
<TextBlock Grid.Column="6" >Date Applied</TextBlock>
<TextBlock Grid.Column="7" >Number Approved</TextBlock>
<TextBlock Grid.Column="8" >Industry</TextBlock>
<Border x:Name="TimelineBorder"  
Background="LightGray"  
BorderBrush="Black"  
BorderThickness="1"
Margin="0,20,140,0" VerticalAlignment="Top" Grid.ColumnSpan="9">
</Border>
</Grid>
</UserControl>
Now that we have the page header set up, we're gonna use that user control in our code along with the printing procedure. In my case i have a datagrid with paging, so i want to print all the data and not just the data displayed on the screen. To do that I did a small workaround, basically setting the page size to the Total Item Count before printing and set the size back to its original state. I dont like this work around but works fine for now. This will alllow us to print every row returned by our datagrid. Furthemore, I created another user control that's gonna act as my data container.
<UserControl x:Class="DB.Silverlight.Views.ApplicationPrintView"
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"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:converter="clr-namespace:DB.Silverlight"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="422" Width="1100">
<UserControl.Resources>
<converter:ApplicationStatusConverter x:Key="ApplicationStatusConverter" />
</UserControl.Resources>
<Grid Margin="20,0">
<StackPanel Name="appData" Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding FirstName}" />
<TextBlock Width="100" Text="{Binding LastName}" />
<TextBlock Width="100" Text="{Binding PhoneNumber}" />
<TextBlock Width="130" Text="{Binding BusinessName}" TextWrapping="Wrap" />
<TextBlock Width="30" Text="{Binding TierCode}" />
<TextBlock Width="90"  Text="{Binding applicationStatus,Converter={StaticResource ApplicationStatusConverter}}" />
<TextBlock Width="90" Text="{Binding DateApplied}" />
<TextBlock Width="100" Text="{Binding NumberApproved}" TextAlignment="Center"  />
<TextBlock Width="290" Text="{Binding IndustryName}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</UserControl>
ApplicationPrintView is the container that binds all fields of my datagrid(which lives in mainpage.xaml) by assigning the binding property which ties it to the PagedCollectionView. And below is the button click event that fires printing.
private void PrintGrid_Click(object sender, RoutedEventArgs e)
{
PrintDocument pd = new PrintDocument();
int ItemIndex = 0;
pd.PrintPage += (s, e) =>
{
PagedCollectionView items = (PagedCollectionView)dgDashboard.ItemsSource;
StackPanel itemHost = new StackPanel();
ApplicationPrintHeadView head = new ApplicationPrintHeadView();
itemHost.Children.Add(head);
items.PageSize = items.TotalItemCount;
try
{
while (ItemIndex < items.TotalItemCount)
{
ApplicationPrintView itemsContainer = new ApplicationPrintView();
itemsContainer.appData.DataContext = items[ItemIndex];
itemHost.Children.Add(itemsContainer);
itemHost.Measure(new Size(e.PrintableArea.Width, double.PositiveInfinity));
if (itemHost.DesiredSize.Height > e.PrintableArea.Height && itemHost.Children.Count > 1)
{
itemHost.Children.Remove(itemsContainer);
e.HasMorePages = true;
break;
}
ItemIndex += 1;
}
}
catch (Exception)
{
MessageBox.Show("An Unexpected Error Has Occured.  Please try again.");
}
finally
{
items.PageSize = 20;
}
e.PageVisual = itemHost;
};
pd.Print("My Document");
}
This will print all data from datagrid across multiple pages with a nicely formatted page header and spaced up page footer so no row gets cut.
In summary, this is what i did to set up printing: in my silverlight project I added a folder called Views then added 2 user controls(page header and container) to it. And ofcourse my datagrid lives in mainPage.xaml that's pulling its data thru WCF (I reused the same service used by my ASP.NET application). Another option would be to use Ria Services. I had my silverlight project configured to be hosted in my ASP.NET application, so all i had to do is build the silverlight project to update the xap file.

References: http://channel9.msdn.com/learn/courses/Silverlight4/