CanOz Blog

Neil Knobbe's Blog at vbCity

vbCity Blogs moved to:
http://cs.vbcity.com/blogs
  Home :: Syndication  :: Login

OctNovember 2009Dec
SMTWTFS
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

Archives

Image Galleries

vbCity Blogs

I have just discovered the most compelling reason to port over any projects I have to Windows Presentation Foundataion (WPF) as well as only starting new projects as a WPF project.

The reason you ask?  Well the answer is one word.

Printing

Being a File I/O guy, printing has almost always needed to be a part of most projects that I have written so this is a really big thing for me.  Looking back printing with Visual Basic.NET was painful for me at times especially if the document spanned several pages and I don't even want to get into printing with pre .NET Visual Basic.
That, however, is the past and today we take a look at the present and future.

After creating a table in a flowdocument I got to thinking about being ability to print out the flowdocument.

The WPF PrintDialog class has two methods for printing.  The two methods are PrintVisual and PrintDocument.

This post will deal with the PrintDocument method.

When you use PrintDocument you don’t actually pass the flowdocument that you want to print as a parameter, you use a DocumentPaginator object.

What the documentpaginator does is take the content of the flowdocument and breaks it up into multiple pages (if required) then passes the page(s) to the printer to be printed out.

After importing the necessary namespaces and declaring two Form level variables

 

 

I started out with the code to create a flowdocument that goes over more than one page I used the following code.

 

' Create the parent FlowDocument...

        flowDoc = New FlowDocument()

 

        Dim table2 As New Table()

 

        flowDoc.Blocks.Add(table2)

 

        table2.Columns.Add(New TableColumn)

        table2.Columns.Add(New TableColumn)

        table2.Columns.Add(New TableColumn)

        table2.Columns.Add(New TableColumn)

 

 

 

        ' Create and add an empty TableRowGroup to hold the table's Rows.

        table2.RowGroups.Add(New TableRowGroup())

 

        ' Add the first (title) row.

        table2.RowGroups(0).Rows.Add(New TableRow())

 

        ' Alias the current working row for easy reference.

        Dim currentRow2 As New TableRow()

        currentRow2 = table2.RowGroups(0).Rows(0)

 

 

        Dim v As New Image

        v.Source = New BitmapImage(New Uri("dice.jpg", UriKind.Relative))

        v.Height = 80

        v.Width = 80

 

 

 

        ' Add cells with content to the second row.

        currentRow2.Cells.Add(New TableCell(New Paragraph(New Run("Loan Report"))))

 

        currentRow2.Cells(0).ColumnSpan = 3

        currentRow2.FontSize = 50

        currentRow2.Foreground = Brushes.RosyBrown

        currentRow2.Cells(0).TextAlignment = TextAlignment.Center

 

        currentRow2.Cells.Add(New TableCell(New BlockUIContainer(v)))

 

        table2.RowGroups(0).Rows.Add(New TableRow())

 

        currentRow2 = table2.RowGroups(0).Rows(1)

 

 

 

        currentRow2.Cells.Add(New TableCell(New Paragraph(New Run(" For: Neil Knobbe, " & Now.Date.ToLongDateString))))

 

        currentRow2.Cells(0).TextAlignment = TextAlignment.Center

        currentRow2.Cells(0).ColumnSpan = 4

 

        table2.RowGroups(0).Rows.Add(New TableRow())

 

        currentRow2 = table2.RowGroups(0).Rows(2)

 

        currentRow2.Cells.Add(New TableCell(New Paragraph(New Run("Ammount Borrowed: Borrowed Variable" & vbCrLf & _

                                                                  "Monthly Payment: Payment Variable" & vbCrLf & _

                                                                  "Interest: Interest Variable" & vbCrLf & _

                                                                  "Duration: Duration Variable" & vbCrLf & _

                                                                  "Reason For Loan: Reason Varaible" & vbCrLf & _

                                                                  "Lender: Lender Variable" & vbCrLf & _

                                                                  "Loan Number: LoanNum Variable"))))

 

 

        currentRow2.Cells(0).ColumnSpan = 2

        currentRow2.Cells(0).Padding = New Thickness(10)

 

        ' Create and add an empty TableRowGroup to hold the table's Rows.

        table2.RowGroups.Add(New TableRowGroup())

 

        ' Add the first (title) row.

        table2.RowGroups(1).Rows.Add(New TableRow())

 

        ' Alias the current working row for easy reference.

        Dim currentRow As New TableRow()

        currentRow = table2.RowGroups(1).Rows(0)

 

        ' Add the header row with content,

        currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Date"))))

        currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Balance"))))

        currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Payment"))))

        currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Interest"))))

 

        currentRow.Cells(0).Foreground = Brushes.RosyBrown

        currentRow.Cells(0).TextAlignment = TextAlignment.Left

        currentRow.Cells(1).Foreground = Brushes.RosyBrown

        currentRow.Cells(1).TextAlignment = TextAlignment.Left

        currentRow.Cells(2).Foreground = Brushes.RosyBrown

        currentRow.Cells(2).TextAlignment = TextAlignment.Left

        currentRow.Cells(3).Foreground = Brushes.RosyBrown

        currentRow.Cells(3).TextAlignment = TextAlignment.Left

 

        table2.RowGroups(1).Rows.Add(New TableRow())

 

        currentRow = table2.RowGroups(1).Rows(1)

 

        currentRow.Cells.Add(New TableCell())

 

        currentRow.Cells(0).ColumnSpan = 4

        currentRow.Cells(0).BorderThickness = New Thickness(0, 5, 0, 0)

        currentRow.Cells(0).BorderBrush = Brushes.RosyBrown

 

        Dim i As Integer

 

        For i = 2 To 25

            table2.RowGroups(1).Rows.Add(New TableRow())

 

            currentRow = table2.RowGroups(1).Rows(i)

 

            currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))

            currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))

            currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))

            currentRow.Cells.Add(New TableCell(New Paragraph(New Run("Column " & i - 1 & " Entry - " & i.ToString))))

 

 

        Next

 

        ScrollView1.Document = flowDoc

 

Which, once I applied the table to a flowdocument and set the flowdocument to be the document of my FlowDocumentScrollViewer,

 

 

gave me a table with 50 entries, shown in three columns, which I wanted to print.

 

 

Now that the flowdocument was ready to print all that was left to do was create instance of a WPF PrintDialog and enter the one line of code needed to print the document.

Yes you heard right, one line of code* to print a WPF flowdocument.  How much easier can it get?  (And I thought I was happy when the people at MS made reading the entire contents of a file into a single line of code for Visual Studio 2005.  I am simple ecstatic about this change to printing!)

(* Note – Although you can accomplish printing with just this one line, there are a few gotchas that you will need to be aware of.  I will touch on some of these in a bit.)

So, the magic line of code is:

 

 

Ok, I hear all you out there going "Ummm.... Neil that looks like 5 lines of code to me not one like you said."  Which would be a valid statement and it is true there is a couple more lines of code. 

Basically what I have done is put the print code in an If...End If statement which gives displays the printdialog to the user where they can choose their printer and print the document or cancel the print job all together.
 
All in all six lines of code to print out a single or multiple page document is nothing.

What is the code doing then?

As I mentioned earlier a new instance of a printdialog is created.  The printdialog is the window that is shown where you can select which printer you want to print with and other printing options.

The printdialog is shown as a dialog window and if the user clicks the "OK" button, the flowdocument is cast as a DocumentPaginator object which is used as the first parameter of the printdialog’s PrintDocument.  The second parameter of the printdocument is the name of the document being printed.  This name is what is displayed in the print queue of the printer so you can uniquely identify what documents are lined up to be printed.

The Else portion of the If...End If statement handles if the user clicks the "Cancel" button.  A simple message box is displayed letting the user know that the print job has been cancelled.

That in a nutshell is all that you need to do to print a flowdocument in a WPF application.

Now for the gotchas that caught me while I tried to print.

#1)  Multiple columns.  Let me tell you I this one really got me.  When I first tried to print the flowdocument in the image above, I expected the print out to look like the window.  Such was not the case.  What I ended up with was the entire table printed out on two columns per page over the three pages* the printed document covered.

 

 

This was not quite what I was expecting, but is apparently the default behaviour without having set any properties of the document to match the printer.  What I wanted was to have the table fill the entire width of the page.

(* Note - As a rather interesting side note here this is not what I got the first time I tried to print this flowdocument.  My first results were rather disappointing and I was very disillusioned with what I thought was going to be a much simplified printing process.  The image below is my first attempt at printing.

 

 

As you can see it ended up quite the mess.  What happened was that all three pages of the flowdocument got printed in two columns on one page.  (If you look real close at the left column you can see the header of the table in the background then the first 8 lines have been printed twice.) 

I can happily report that the fault with this lies with the printer and not how the WPF printdialog sent the document to the printer.  I have since tested several other printers and they all print as the multiple columns image.)

Getting back on track and printing the document on only one column.

The way to get around having multiple columns is to set the column width of the document to be printed prior to casting it as a documentpaginator object.  This is a simple process and requires only one line of code.

 

 

What this code does is sets the width of document in the scrollviewer to be the entire printable width of the page.  Now when the document is printed the table will span the width of the page.

#2) Page size.  While perhaps not quite as important as multiple columns is getting the proper page size and margins for your printed output.

You can set the bounds of your document to be printed by setting three properties of the document.  The three properties are the PrintableAreaHeight, PrintableAreaWidth and the Thickness. 

The PrintableAreaHeight and PrintableAreaWidth properties are pretty self explanatory.  The Thickness is what is used for the margin.  The thickness can be set with a universal value which will be applied to all sides, or you can set pass different parameters for the margin on each side.

 

 

While it is not essential to set these three properties for printing it does ensure that your document fits the page or pages.

Summary

So, with adding in the few lines of code from the two gotchas, I finally was able to print out the report and have it look as I wanted it to.

 

 

The entire print code I used was:

 

 

There you have it a quick overview of printing in a WPF application.  As I said it is much easier and I have my doubts if I will ever look at starting another WinForms application.

posted on Saturday, October 18, 2008 1:05 AM

Feedback

# Great post, but its a bit long and most people like short and sweet posts! 3/21/2009 6:56 PM Simonn
Keep working ,great job!

# re: Printing a Windows Foundation Presentation FlowDocument 6/17/2009 2:14 AM Arne Wauters
This post has been my main inspiration in solving several printing issues. The results are fantastic. Thank you

Post Feedback

Title:
Name:
Url:
Comments: 
Protected by Clearscreen.SharpHIPEnter the code you see: