In short, the philosophy of Silverlight printing is focused on two areas.
- First one is two take an UIElement from application VisualTree and print it, as it is. The main advantage is that all user action, like selected gridview row, checkbox button... are printed out.
- The second atitude is to create (recreate) UIElements in code-behind and print it out. The advantege of this is that you can layout your elements in fancy way in order to have nice print. The disadvantege is, that you need to make extra programming work.
The only problem with this attitude is, that all content which do not fit to the print page is truncated.
The solution for this, is to rescale the content with transformation to fit the print page:
double scale = 1;
if (e.PrintableArea.Height < this.Target.ActualHeight)
{
scale = e.PrintableArea.Height / this.Target.ActualHeight;
}
if (e.PrintableArea.Width < this.Target.ActualWidth && e.PrintableArea.Width / this.Target.ActualWidth < scale)
{
scale = e.PrintableArea.Width / this.Target.ActualWidth;
}
if (scale < 1)
{
ScaleTransform scaleTransform = new ScaleTransform();
scaleTransform.ScaleX = scale;
scaleTransform.ScaleY = scale;
this.Target.RenderTransform = scaleTransform;
}
e.PageVisual = this.Target;
Now the printed UIElement fits to the printed page, but during printing in some situations user can see rescaled printing element, which is not a nice user experience. In order to eliminate this behavior, I have used an extra UIElement that will hide the printed part:
if (this.Target.Parent is Grid)
{
_parentGrid = (Grid)this.Target.Parent;
_border = new Border();
_border.BorderBrush = new SolidColorBrush(Colors.Black);
_border.BorderThickness = new Thickness(1);
_border.Background = new SolidColorBrush(Colors.LightGray);
_border.Child = new TextBlock() { Text = "Printing...", HorizontalAlignment= HorizontalAlignment.Center, VerticalAlignment=VerticalAlignment.Center };
_parentGrid.Children.Add(_border);
}
To simplify the code, I have made the prerequisite, that Target UIElement is hosted on Grid. If not hosted on Grid, then the curtain will not show.
For removing curtain after printing has done some code should be placed in EndPrint event handler.
Now, in order to have a nice, reusable piece of code I have created TargetedTriggerAction.
public class PrintTargetedTrigger : TargetedTriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
Execute();
}
/// <summary>
/// Executes the print action of the target object.
/// </summary>
public void Execute()
{
if (this.Target == null)
throw new NullReferenceException("The target object is null.");
PrintDocument pd = new PrintDocument();
pd.PrintPage += new EventHandler<PrintPageEventArgs>(pd_PrintPage);
pd.BeginPrint += new EventHandler<BeginPrintEventArgs>(pd_BeginPrint);
pd.EndPrint += new EventHandler<EndPrintEventArgs>(pd_EndPrint);
pd.Print("");
}
...
}
To initialize printing trigger in xaml:
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<trigger:PrintTargetedTrigger TargetName="TargetElementName"></trigger:PrintTargetedTrigger>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
in code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
PrintTargetedTrigger printTargetedTrigger = new PrintTargetedTrigger();
printTargetedTrigger.TargetObject = this.TargetElementName;
printTargetedTrigger.Execute();
}
Sample solution on my skydrive: