Posts Tagged ‘Printing’

How to print a List<string> in Silverlight 4 Beta over multiple pages?!

Thursday, December 10th, 2009

On www.silverlight.net several people are asking how to print the values of a DataGrid in Silverlight. You cannot just assign the DataGrid to the PageVisual-Property of the PrintPageEventArgs. This would just print the DataGrid as it is on one page. The data wouldn’t be splitted on several pages, cause there’s no paging logic to use. You’ve to write this logic.

You’re responsible for the paging! But how to do it? In this post I’ll give you a little idea how it could work by simple printing out a List of string-values. I won’t talk a lot about the details. Just look at the code below:

public partial class MainPage : UserControl
{
  private List<string> _list;
  private const double ROWHEIGHT = 20;
  private const double PAGEMARGIN = 30;
  public MainPage()
  {
    InitializeComponent();
    _list = new List<string>();

    for (int i = 1; i < 101; i++)
    {
      _list.Add(i + " thanks to Thomas for this printing sample");
      _list.Add("Visit http://www.thomasclaudiushuber.com");
    }
  }

  private void Button_Click(object sender, RoutedEventArgs e)
  {
    _currentIndex = 0;

    var pd = new PrintDocument();
    pd.DocumentName = "AListFromThomas";
    pd.PrintPage += pd_PrintPage;
    pd.Print();

  }

  private int _currentIndex;
  private double _currentTop;
  private double _availableSpace;
  void pd_PrintPage(object sender, PrintPageEventArgs e)
  {
    var pageRoot = new Canvas();
    e.PageVisual = pageRoot;

    _currentTop = PAGEMARGIN;
    _availableSpace = e.PrintableArea.Height - PAGEMARGIN*2;
    while (_currentIndex < _list.Count)
    {
      var txt = new TextBlock { Text = _list[_currentIndex] };

      if (ROWHEIGHT > _availableSpace)
      {
        e.HasMorePages = true;
        break;
      }

      txt.SetValue(Canvas.TopProperty, _currentTop);
      txt.SetValue(Canvas.LeftProperty, PAGEMARGIN);
      pageRoot.Children.Add(txt);
      _currentTop += ROWHEIGHT;
      _availableSpace -= ROWHEIGHT;
      _currentIndex++;
    }
  }
}

When the Button_Click-Eventhandler is executed, the List gets printed over 5 pages. You can easily print it to PDFCreator or XPS Printer to test it. The output looks like this:

image

Download the source here and enjoy. Give me feedback by entering a comment to this blogentry or via email on www.thomasclaudiushuber.com/blog.

How to print dynamically created Images in Silverlight 4 Beta

Wednesday, November 25th, 2009

Silverlight 4 supports printing scenarios. It’s quite easy. Just create a PrintDocument instance, handle the PrintPage-Event and call the Print-Method. In the PrintPage-Event set the PageVisual-Property of the PrintPageEventArgs to a UIElement of your choice. If there are more pages, set the HasMorePages-Property of the PrintPageEventArgs to true and the PrintPage-Eventhandler would be called again for the next page.

Below a simple example using a lambda expression. When the Print-Method is called a PrintDialog is displayed to the User, where he can select the printer of his choice. When the PrintDialog was accepted, the PrintPage-Event gets fired and the lambda expression below get’s called. The PageVisual-Property is set to a TextBlock. So that TextBlock with the text “Thoams says…” is printed out.

var pd = new PrintDocument();
pd.PrintPage += (s, e) =>
  {
    e.PageVisual = new TextBlock {Text="Thomas says Hello"};
  };
pd.Print();

Ok, so far so good. As I was working on an example for my upcoming Silverlight 4 book I needed to create an Image-Element on the fly and print this out. And then I noticed that the Image doesn’t appear on the output.

While searching for a solution I found somebody having the same problem in this thread in Microsoft’s Silverlight forums:

http://forums.silverlight.net/forums/t/145680.aspx

So, it seemed it was not my cause, it was a Beta-cause. So let’s look at a workaround. But first look at the bug.

I made a smaller example to reproduce it. View the following code. What do you think is printed on the page?

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var streamResourceInfo =
    Application.GetResourceStream(
      new Uri("thomas.png", UriKind.Relative));

  var bitmapImage = new BitmapImage();
  bitmapImage.SetSource(streamResourceInfo.Stream);

  var image = new Image
  {
    Width = bitmapImage.PixelWidth,
    Height = bitmapImage.PixelHeight,
    Source = bitmapImage
  };

  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = image;
    };
  pd.Print();
}

Right, an Image should be printed on the page. But it isn’t. The page is empty. Well, the next thing I tried was to call Measure, Arrange and UpdateLayout on the Image to force a layout-pass. But anyway, it didn’t work, the printed page is always empty.

When the Image isn’t created on the fly, it works. Define the Image in XAML like this

<Image Source="thomas.png" x:Name="image"/>

and a Print-Method in the Codebehind-File would work like that:

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = image;
    };
  pd.Print();
}

But we want to print an Image on the fly. So how to do that? One way I found out was to create an ImageBrush and set its ImageSource-Property to the BitmapImage. Use the ImageBrush for a Rectangle’s Fill-Property and print out that Rectangle. So here is some code to dynamically print an image by using an ImageBrush in combination with a Rectangle:

void PrintButton_Click(object sender, RoutedEventArgs e)
{
  var streamResourceInfo =
    Application.GetResourceStream(
      new Uri("thomas.png", UriKind.Relative));

  var bitmapImage = new BitmapImage();
  bitmapImage.SetSource(streamResourceInfo.Stream);

  var imageBrush = new ImageBrush();
  imageBrush.ImageSource = bitmapImage;

  var rectangle = new Rectangle
  {
    Width = bitmapImage.PixelWidth,
    Height = bitmapImage.PixelHeight,
    Fill = imageBrush
  };

  var pd = new PrintDocument();
  pd.PrintPage += (s, args) =>
    {
      args.PageVisual = rectangle;
    };
  pd.Print();
}

And voilà, the output looks like this when printed to my PDFCreator-Printer:

image

[Download the Source]

Cheers Thomas

WPF Printing: How to print a PageRange with WPF’s PrintDialog – that means the user can select specific pages and only these pages are printed

Tuesday, November 24th, 2009

Printing a Page Range isn’t as easy as it supposed to be. So in this blog-post you’ll see a very easy method to print specific pages from a XPS-Document. But before we look at the solution, let’s start with the problem.

The Problem

WPF’s PrintDialog has a UserPageRangeEnabled-Property. Set this property to true before you show the PrintDialog. Then the user is allowed to enter a Pagerange in the PrintDialog. The problem is that whatever the user enters, in each case all pages are printed. The PrintDialog itself has no logic to print some specific pages. Let’s look at an example.

Below the small UI that consists of a Button and a DocumentViewer. This code belongs to the MainWindow.xaml-File.

<Button Content="Print" Click="PrintButtonClick"
        Margin="10"
        HorizontalAlignment="Left"
        Width="75"/>
<DocumentViewer x:Name="viewer" Grid.Row="1"/>

The Codebehind-File MainWindow.xaml.cs has an EventHandler for the Loaded-Event of the Window. There a XPS-Document with five pages is loaded into memory. The FixedDocument-Instance contained in that XPS-Document is stored in the _fixedDocument-Field and displayed in the DocumentViewer called viewer – the latter we declared above in XAML.

public partial class MainWindow : Window
{
 private FixedDocument _fixedDocument;

 public MainWindow()
 {
   InitializeComponent();
 }

 private void OnLoaded(object sender, RoutedEventArgs e)
 {
    // Load the FixedDocument
  var document = new XpsDocument("FivePagesDocument.xps",
                                   FileAccess.Read);
  var sequence = document.GetFixedDocumentSequence();
  _fixedDocument = sequence.References[0].GetDocument(false);

    // Assign it to the viewer
  viewer.Document = _fixedDocument.DocumentPaginator.Source;
 }

So we have the necessary thing for testing a “print-range”-scenario: A FixedDocument-Instance in memory, stored in the _fixedDocument-Field. Let’s look at the PrintButtonClick-Eventhandler that contains the logic to print. The Eventhandler is shown below. First a PrintDialog is created. Then we set the UserPageRangeEnabled-Property to true, show the Dialog by calling the ShowDialog-Method. Is the return-value true we call the PrintDialog’s PrintDocument-Method by passing in the DocumentPaginator of the FixedDocument. Also a string is passed in that is displayed in the printer queue as jobname.

void PrintButtonClick(object sender, RoutedEventArgs e)
{
  var dlg = new PrintDialog();

  // Allow the user to select a PageRange
  dlg.UserPageRangeEnabled = true;

  if (dlg.ShowDialog() == true)
  {
    DocumentPaginator paginator = 
      _fixedDocument.DocumentPaginator;
    dlg.PrintDocument(paginator, "Just a test");
  }
}

Now what happens? Let’s look how we use the application. When starting up the application, the FixedDocument with five pages is displayed in the DocumentViewer:

image

When the “Print”-Button is pressed, the PrintDialog comes up. I select a PageRange of 2-3 like shown below. For testing purposes I always print in the PDFCreator that creates a PDF-Document with the output. There are several other ways. You could also print in the XPS-Document-Writer (even if it’s a little bit doubled when printing a xps to the XPS-Document-Writer).

image

Now even with the Pages of 2-3 selected, the “printed” PDF looks like below. It contains all five pages of my document. But it should contain only the pages 2 and 3.

image

So let’s summarize the problem: It doesn’t matter what you select as a PageRange in the PrintDialogs Pages-Field. It’s up to you to check the Pages and print out only that range. The PrintDialog doesn’t care anything about the PageRange.

The PrintDialog has the Property PrintRange that contains a PrintRange–object with PrintFrom- and PrintTo-Properties. It also has a Property called PageRangeSelection that contains the value PageRangeSelection.UserPages when a PageRange was entered. But the logic to print out that specified range is your work. And this work needs some WPF know-how to do. So let’s look at the solution(s). :-)

A bad solution

I call the first one a bad solution because it is not an easy to manage one. This solution is to make a new FixedDocument that only contains the pages you need. You can implement such a logic based on the things you find in this codeproject-article:

http://www.codeproject.com/KB/vb/reording_xps.aspx

In my mind creating a new XPS-Document just for printing is a bad solution. I think a good solution would be to just be able to print some pages of an existing xps-Document/FixedDocument. So let’s look at the good one.

A good solution (I hope so ;-))

The good solution is the one I’ve implemented today and finished in the train from Zurich to Basel. I’ve not tested every scenario, but it seems to work great. If you have any hints to this solution, let me know.

The first thing you have to know is that the pages used for the output are created by a DocumentPaginator. This was passed to the PrintDocument-Method of the PrintDialog some lines above. The DocumentPaginator-class itself is abstract. Beside some members it defines a Method GetPage(int pageNumber) that returns the DocumentPage for the passed in pageNumber. Now the idea is to create a wrapper-class for the DocumentPaginator that is itself also of type DocumentPaginator. And now guys, what pattern is that? ;-) Doesn’t matter.

So we create a DocumentPaginator and let’s call it PageRangeDocumentPaginator. It encapsulates a DocumentPaginator and can return a specific (page)range of the encapsulated DocumentPaginator. Therefore the constructor takes two parameters: a DocumentPaginator to encapsulate and a PageRange-object that contains PageFrom and PageTo-Properties, both of type int. The PageRange-structure exists in the Namespace System.Windows.Controls.

The PageRangeDocumentPaginator-class is listed below. In the constructor the passed in DocumentPaginator is stored in the _paginator-field. The pageRange-values are stored in the fields _startIndex and _endIndex. As the PageRange starts with 1, we substract 1 to get the zero-based index. In the final line of the constructor the _endIndex is adjusted. If the user enters a higher pageNumber than the Document contains, _endIndex will point to the index of the last page.

Now look at the override of the GetPage-Method. There gets just the GetPage-Method of the encapsulated DocumentPaginator called. But the _startIndex is regarded. Now look at the PageCount-Property. It uses the _startIndex and _endIndex to calculate the pageNumbers. IsPageCountValid always returns true and the Properties PageSize and Source simply pass the responsibility to the underlying DocumentPaginator. That’s it. Now look how to use the class in the PrintButtonClick-Eventhandler.

/// <summary>
/// Encapsulates a DocumentPaginator and allows
/// to paginate just some specific pages (a "PageRange")
/// of the encapsulated DocumentPaginator
///  (c) Thomas Claudius Huber 2010 
///      http://www.thomasclaudiushuber.com
/// </summary>
public class PageRangeDocumentPaginator : DocumentPaginator
{
  private int _startIndex;
  private int _endIndex;
  private DocumentPaginator _paginator;
  public PageRangeDocumentPaginator(
    DocumentPaginator paginator,
    PageRange pageRange)
  {
    _startIndex = pageRange.PageFrom - 1;
    _endIndex = pageRange.PageTo - 1;
    _paginator = paginator;

    // Adjust the _endIndex
    _endIndex = Math.Min(_endIndex, _paginator.PageCount - 1);
  }
  public override DocumentPage GetPage(int pageNumber)
  {
    // Just return the page from the original
    // paginator by using the "startIndex"
    return _paginator.GetPage(pageNumber + _startIndex);
  }

  public override bool IsPageCountValid
  {
    get { return true; }
  }

  public override int PageCount
  {
    get
    {
      if (_startIndex > _paginator.PageCount - 1)
        return 0;
      if (_startIndex > _endIndex)
        return 0;

      return _endIndex - _startIndex + 1;
    }
  }

  public override Size PageSize
  {
    get{return _paginator.PageSize;}
    set{_paginator.PageSize = value;}
  }

  public override IDocumentPaginatorSource Source
  {
    get { return _paginator.Source; }
  }
}

The PrintButtonClick-Eventhandler just needs a little if-statement. The PrintDialog has a Property called PageRangeSelection of Type PageRangeSelection (Enum). The PageRangeSelection-Enum contains the values AllPages and UserPages. When the User has entered a PageRange, that Property would have the value UserPages. In that case I use the PageRangeDocumentPaginator-class and pass in the DocumentPaginator of the FixedDocument and the PageRange from the PrintDialog. If the PageRangeSelection-Property contains not the value UserPages, we print all pages as we’ve already done in the problem-scenario. So the code looks like this:

void PrintButtonClick(object sender, RoutedEventArgs e)
{
  var dlg = new PrintDialog();

  // Allow the user to select a PageRange
  dlg.UserPageRangeEnabled = true;

  if (dlg.ShowDialog() == true)
  {
    DocumentPaginator paginator = 
      _fixedDocument.DocumentPaginator;

   if (dlg.PageRangeSelection == PageRangeSelection.UserPages)
   {
     paginator = new PageRangeDocumentPaginator(
                      _fixedDocument.DocumentPaginator,
                      dlg.PageRange);
   }

    dlg.PrintDocument(paginator, "Yes, it works");
  }
}

When the user has selected a PageRange the PageRangeDocumentPaginator is used to generate the pages for the output. When I test the code with a PageRange of 2-3 as in the problem-section, I now retrieve the expected output of the two pages 2 and 3, as my “printed” PDF shows:

image

That’s it. Enjoy the code and download the solution here. And don’t forget to kick this post for me:

[Download Thomas PrintingProject]

kick it on DotNetKicks.com

Cheers Thomas

PS: As time and my sparetime is limited – I’m writing on my Silverlight 4.0 book and an update of my WPF book in my sparetime – I can’t give any free support for the code. Use it or not. But feel free to contact me for any suggestions via:

http://www.thomasclaudiushuber.com/contact.php