19

I'm using a class I've derived from DocumentPaginator (see below) to print simple (text only) reports from a WPF application. I've got it so that everything prints correctly, But how do I get it to do a print preview before printing? I have a feeling I need to use a DocumentViewer but I can't figure out how.

Here's my Paginator Class:

public class RowPaginator : DocumentPaginator
{
    private int rows;
    private Size pageSize;
    private int rowsPerPage;

    public RowPaginator(int rows)
    {
        this.rows = rows;
    }

    public override DocumentPage GetPage(int pageNumber)
    {
        int currentRow = rowsPerPage * pageNumber;
        int rowsToPrint = Math.Min(rowsPerPage, rows - (rowsPerPage * pageNumber - 1));
        var page = new PageElementRenderer(pageNumber + 1, PageCount, currentRow, rowsToPrint)
                       {
                           Width = PageSize.Width,
                           Height = PageSize.Height
                       };
        page.Measure(PageSize);
        page.Arrange(new Rect(new Point(0, 0), PageSize));
        return new DocumentPage(page);
    }

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

    public override int PageCount { get { return (int)Math.Ceiling(this.rows / (double)this.rowsPerPage); } }

    public override Size PageSize
    {
        get { return this.pageSize; }
        set
        {
            this.pageSize = value;
            this.rowsPerPage = PageElementRenderer.RowsPerPage(this.pageSize.Height);
            if (rowsPerPage <= 0)
                throw new InvalidOperationException("Page can't fit any rows!");
        }
    }

    public override IDocumentPaginatorSource Source { get { return null; } }
}

The PageElementRenderer is just a simple UserControl that displays the data (at the moment just a list of rows).

Here's how I use my Row Paginator

PrintDialog dialog = new PrintDialog();
if (dialog.ShowDialog() == true)
{
    var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };

    dialog.PrintDocument(paginator, "Rows Document");
}

Sorry for the code dump, but I didn't want to miss something relevant.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
Ray
  • 45,695
  • 27
  • 126
  • 169
  • see also: http://stackoverflow.com/questions/2322064/how-can-i-produce-a-print-preview-of-a-flowdocument-in-a-wpf-application – Cheeso Mar 04 '10 at 19:10

6 Answers6

21

So I got it working after reading Pro WPF in C# 2008 (Page 726).

Basically the DocumentViewer class needs an XPS file to present a print preview of it. So I do the following:

PrintDialog dialog = new PrintDialog();
var paginator = new RowPaginator(rowsToPrint) { PageSize = new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight) };

string tempFileName = System.IO.Path.GetTempFileName();

//GetTempFileName creates a file, the XpsDocument throws an exception if the file already
//exists, so delete it. Possible race condition if someone else calls GetTempFileName
File.Delete(tempFileName); 
using (XpsDocument xpsDocument = new XpsDocument(tempFileName, FileAccess.ReadWrite))
{
    XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
    writer.Write(paginator);

    PrintPreview previewWindow = new PrintPreview
                                     {
                                         Owner = this,
                                         Document = xpsDocument.GetFixedDocumentSequence()
                                     };
    previewWindow.ShowDialog();
}

I'm creating the print dialog to get the default page size. There's probably a better way to do this. XpsDocument is in ReachFramework.dll (Namespace System.Windows.Xps.Packaging);

Here's the PrintPreview Window.

<Window x:Class="WPFPrintTest.PrintPreview"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="previewWindow"
    Title="PrintPreview" Height="800" Width="800">
    <Grid>
        <DocumentViewer Name="viewer" 
                        Document="{Binding ElementName=previewWindow, Path=Document}" />
    </Grid>
</Window>

The code behind just has a Document property like so:

public IDocumentPaginatorSource Document
{
    get { return viewer.Document; }
    set { viewer.Document = value; }
}
Ray
  • 45,695
  • 27
  • 126
  • 169
  • 4
    FYI, you can use System.IO.Path.GetRandomFileName() (http://msdn.microsoft.com/en-us/library/system.io.path.getrandomfilename.aspx) in place of Path.GetTempFileName() to get a filename without creating the file. – Cheeso Feb 23 '10 at 22:34
7

The following code uses a MemoryStream to do a print preview.

MemoryStream stream = new MemoryStream();

Package package = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);

var uri = new Uri(@"memorystream://myXps.xps");
PackageStore.AddPackage(uri, package);
var xpsDoc = new XpsDocument(package);

xpsDoc.Uri = uri;
XpsDocument.CreateXpsDocumentWriter(xpsDoc).Write(paginator);

documentViewer.Document = xpsDoc.GetFixedDocumentSequence();

Remember to use close() and remove package from PackageStore when the print preview is closed.

Bobsum
  • 71
  • 1
  • 2
3

WPF doesn't come with any built-in Print Preview functionality, if you want to do a print preview, you're going to have to construct it yourself. Fortunately, it's shouldn't be that difficult.

You've already got the pagination code, which creates your DocumentPage objects. These objects contain a Visual, which you can go ahead and display in your UI.

What you'll end up doing, is paginating your entire document, collecting all the DocumentPage objects, then displaying their visuals inside of a scrolling StackPanel or something similar. These are the same DocumentPage objects that you can then send to the printer.

Fortes
  • 1,436
  • 2
  • 12
  • 14
1
XpsDocument doc = new XpsDocument("filename.xps", FileAccess.Read);
docViewer.Document = doc.GetFixedDocumentSequence();
doc.Close();
pnb
  • 11
  • 1
0

The WinForm code for print preview is:

PrintPreviewDialog PrintPreviewDialog = new PrintPreviewDialog();
PrintPreviewDialog.Document = PrintDocument;
PrintPreviewDialog.ShowDialog();

P.s.: The original poster has commented that this is the wrong answer for an WPF application.

MrValdez
  • 8,515
  • 10
  • 56
  • 79
0

I dont think there is a built in way of doing this

Here is how I got it working in NHaml

var documentViewHostDialog = new DocumentDialog();
documentViewHostDialog.LoadDocument(load);
documentViewHostDialog.ShowDialog();

Where "load" is either a FlowDocument or IDocumentPaginatorSource and here is the DocumentDialog http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml http://code.google.com/p/nhaml/source/browse/trunk/src/NHaml.Xps/DocumentDialog.xaml.cs

Hope it works for your case.

Simon
  • 33,714
  • 21
  • 133
  • 202