|
2007-07-23 14:42:47 11: Printing
This entry is part 11 of a 12-part series on WPF
3D.
Simple Printing
I've said it before and I'll say it again now: It's just
incredibly cool that WPF's integration of 3D is so thorough that printing is supported.
Hardware accelerated 3D graphics APIs (like OpenGL and
Direct3D) are all about screens, not paper. If you're building a 3D graphics
app, these APIs are great. They not only make things a bit easier, but they offer
a big performance boost as well. But then if you want your app to be able to
print, the API disappears and you're left all alone.
In contrast, WPF makes this relatively simple. Printing a
Viewport3D in WPF looks like this:
PrintDialog dlg = new PrintDialog();
if ((bool)dlg.ShowDialog().GetValueOrDefault())
{
dlg.PrintVisual(myViewport3D, "Sawdust");
}
Five lines. Were it not for my pathological need to use
braces on every if statement, it would be three lines.
XPS
As cool as it is to be able to print so easily, the code
above is rather simplistic. Most applications will want to offer their users
more.
My current printing code for Sawdust is focused on XPS, which is printer-friendly
document file format, conceptually similar to PDF. After constructing an XPS
file, I can spool it to the printer or save it to disk so the user may view,
print or archive it like a PDF file. More people use PDF, but for a WPF 3D
app, XPS is much easier to generate. Start by creating a FixedDocument.
FixedDocument
In the August 2007 issue of MSDN Magazine, Markus Egger has a nice overview of
WPF's FlowDocument class. FlowDocument is designed for viewing
document-oriented content on a screen. Its sister class is FixedDocument,
which is designed for putting document-oriented content on paper.
As you might expect, a FixedDocument is simply a collection
of pages:
public static
void CreateFixedDocument()
{
FixedDocument doc = new FixedDocument();
doc.DocumentPaginator.PageSize = new Size(96 * 8.5, 96 * 11);
foreach (page that I want)
{
PageContent page = new PageContent();
FixedPage fixedPage =
CreateOneFixedPage();
((IAddChild)page).AddChild(fixedPage);
doc.Pages.Add(page);
}
return doc;
}
The IAddChild cast is rather funky. Microsoft's own sample
code shows this technique, but their documentation for IAddChild says: "This
member supports the Microsoft .NET Framework version 3.0 infrastructure and is
not intended to be used directly from your code." Hmmph.
The real work of creating aFixedDocument is in creating each
page. A FixedPage is a UIElement that acts somewhat like a Canvas. To layout
your page, you simply add a bunch of things to it, each with a specific size
and position.
First, create a FixedPage:
FixedPage page = new FixedPage();
page.Background = Brushes.White;
page.Width = 96 * 8.5;
page.Height = 96 * 11;
Now let's add a big title at the top of the page:
TextBlock tbTitle = new TextBlock();
tbTitle.Text = "My Page Title";
tbTitle.FontSize = 24;
tbTitle.FontFamily = new FontFamily("Arial");
FixedPage.SetLeft(tbTitle, 96 * 0.75); // left margin
FixedPage.SetTop(tbTitle, 96 * 0.75); // top margin
page.Children.Add((UIElement)tbTitle);
Now we add our Viewport3D to the page. We want it to be 2
inches from the top of the sheet and 2 inches from the left side. We'll assume
we have already prepared a Viewport3D which is the proper size to fit. I think
I'll draw a thin black border around it as well.
Border b = new Border();
b.BorderThickness = new Thickness(1);
b.BorderBrush = Brushes.Black;
b.Child = myViewport3D;
FixedPage.SetLeft(b, 96 * 2);
FixedPage.SetTop(b, 96 * 2);
page.Children.Add((UIElement)b);
Once you've constructed all the elements on your page, you
need to call Measure(), Arrange() and UpdateLayout() to get it ready for
drawing:
Size sz = new Size(96 * 8.5, 96
* 11);
page.Measure(sz);
page.Arrange(new Rect(new Point(), sz));
page.UpdateLayout();
From FixedDocument to XPS
Once we have constructed a FixedDocument object in memory, what
do we do with it? Writing it out to an XPS file is pretty simple:
FixedDocument doc =
CreateFixedDocument();
XpsDocument xpsd = new
XpsDocument(filename, FileAccess.ReadWrite);
XpsDocumentWriter xw = XpsDocument.CreateXpsDocumentWriter(xpsd);
xw.Write(doc);
xpsd.Close();
Viewing an XPS Document
For viewing XPS files, I recommend you download the XPS
Essentials Pack from Microsoft. It contains a nice viewer application.
For WPF applications, XPS is the way to implement any
serious printing capabilities. I'm no expert with XPS yet, but so far I'm
quite pleased with the results I'm getting.
|