Tuesday, January 29, 2008

Printing more then one page, creation in memory XPS document and DocumentViewer customization

Today, we'll answer number of questions, regarding DocumentViewer, XPSDocument, FlowDocument and more

First question: I'm trying to display FlowDocument, by using DocumentViewer and I'm getting "FlowDocument' object cannot be added to 'DocumentViewer'. DocumentViewer supports only FixedDocument or FixedDocumentSequence documents". What to do?

First answer: DocumentViewer supports only fixed document source (one, which implements IDocumentPaginatorSource - other words supports pagination). In order to do it, we have to convert FlowDocument into FixedDocument and we can do it, by using XpsDocument and it's GetFixedDocumentSequence() method to create the page sequence.

Second question: But I do not want to save anything, I want to do it in memory. What to do?

Second answer: First load your FlowDocument by using XamlReader into IDocumentPaginatorSource (that's what we'll need for DocumentViewer)

<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

    <Paragraph>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam urna augue, semper ut, condimentum et, pharetra ac, massa. Cras tellus lacus, tristique eget, tincidunt vitae, mattis at, eros. Quisque pretium, ante at porttitor accumsan, ipsum enim laoreet tellus, sit amet aliquet felis tortor et lorem. Nullam sodales viverra sapien. Morbi leo magna, dignissim a, sollicitudin at, lacinia posuere, dui. Sed vestibulum elit a ante. Vivamus pellentesque augue sit amet enim. Pellentesque dignissim, lectus at congue elementum, augue felis vulputate ante, eu bibendum dui mauris sed magna. Cras metus dui, ullamcorper id, fermentum ornare, hendrerit non, libero. Donec blandit lorem sit amet velit. Phasellus aliquam. In vel urna sit amet lorem molestie tristique. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla aliquam urna sit amet enim. Integer vulputate mauris non diam. Vestibulum ac mauris. Proin porttitor adipiscing nibh. Phasellus neque. Sed sollicitudin eros in diam. Quisque accumsan, neque non volutpat semper, lectus nunc porttitor libero, at pretium purus velit eget mauris.</Paragraph>

    <Paragraph>Sed ac mauris. Nulla eu augue ut est pellentesque blandit. Phasellus non quam ac neque suscipit vehicula. Donec mauris augue, pulvinar at, vestibulum quis, vulputate et, nunc. Sed ut pede. Praesent ut justo id justo nonummy porttitor. Vivamus vitae massa sit amet massa scelerisque aliquam. Nullam ligula justo, suscipit id, sollicitudin at, pretium a, lorem. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. In hac habitasse platea dictumst. In quam dui, gravida quis, congue ac, rhoncus ac, mi. Donec mattis tempor orci. Mauris ullamcorper. Donec non sem vel tortor imperdiet euismod. Morbi nec eros. Maecenas quis turpis at lorem semper ullamcorper.</Paragraph>

</FlowDocument>

using (Stream io = Assembly.GetExecutingAssembly().GetManifestResourceStream("PagePrint.text.xaml"))
            {
                IDocumentPaginatorSource text = XamlReader.Load(io) as IDocumentPaginatorSource;
                io.Close();
            }

Now, let's create Xps document in memory and load our text into it

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

doc = new XpsDocument(pkg, CompressionOption.SuperFast);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;
rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

We got an exception: "XpsDocument URI is null. Use XpsDocument constructor that takes URI parameter." But we have no URI, we are working in memory!

Third question: What to do?

Third answer: All you have to do is to add another package with URI, that identifies our document and create XPS document, by using the new identifier. We'll add it into current code and now it'll looks as following:

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

string pack = "pack://temp.xps";
PackageStore.AddPackage(new Uri(pack), pkg);

doc = new XpsDocument(pkg, CompressionOption.SuperFast,pack);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;
rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

Well, now it works, but we still have another questions

Forth question: How to set the page size for my document?

Forth answer: You already have DocumentPaginator, why not to use it?

ms = new MemoryStream();
pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);

string pack = "pack://temp.xps";
PackageStore.AddPackage(new Uri(pack), pkg);

doc = new XpsDocument(pkg, CompressionOption.SuperFast,pack);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator pgn = text.DocumentPaginator;

pgn.PageSize = new Size(768, 676);

rsm.SaveAsXaml(pgn);
viewer.Document = doc.GetFixedDocumentSequence();

Well done. Now our page is 8"x6" (1 inch = 96px in default resolution). Now it almost ok, but I want to customize DocumentViewer, used to present our document

Fifth question: How to remove search field ("Type text to find...") or how to customize buttons?

Fifth answer: DocumentViewer is regular WPF control, thus in order to customize it, you should override it's default template

By default the controls looks

image

But if you'll remove <ContentControl Grid.Row="2" x:Name="PART_FindToolBarHost"/> line from the control template, you'll get rid of the search bar. Of couse, you can customize anything you want within the control template of any WPF control.

image

We done. Have a nice XPS/WPF programming.

Source code for this article

No comments: