XPS documents and flow content

XML Paper Specification (XPS) is a combination of a document markup language, which is a subset of XAML, and code support for viewing and printing documents. Although you won't read this in the Microsoft documentation, many see XPS as Microsoft's answer to Adobe's Portable Document Format (PDF) for creating print-ready documents. Fortunately, that debate is irrelevant to us because the classes that provide XPS support are a fantastic way of incorporating documents within WPF applications.

One use for XPS documents is incorporating highly readable content within desktop applications, blurring the line between online and offline applications. One such application is the Mix Reader (figure 9.9) created by Conchango for the Mix UK 2007 conference. It combines offline document reading with online capabilities including RSS reading and Facebook and Twitter integration.

15 Notjust a restriction when working from IronPython. The XamlWriter serialization is explicitly runtime and can't access design-time information.

Ironpython Wpf
Figure 9.9 The Mix Reader, a desktop WPF application with powerful document-reading capabilities

Applications with dynamic content and features are particularly suited to Iron-Python. You can dynamically create (and manipulate) XAML and then load it at runtime using the XamlReader. In this section, we play with XAML while exploring document integration.

XPS documents are packages that can contain one or more fixed documents along with their dependencies such as images and fonts. This format is most similar to PDF, and Microsoft provides an XPS Viewer application16 and printing support. In fact, XPS is now the native Windows Vista print spool file format.

Fixed documents are a relatively low-level format that contains FixedPage elements. They're used when an application needs to control the layout of the document—for example, for printing—and they're the type of document stored in XPS files. Fixed documents must contain certain elements that make up the page, such as width, height, and language.

More interesting, from an application programmer's point of view, are FlowDocu-ments. Flow documents can be viewed with some high-level reader controls and reflow dynamically as the control size changes. They also offer advanced document features

16 See http://www.microsoft.com/whdc/xps/viewxps.mspx.

such as pagination and columns. Of course, these features are better demonstrated than explained.

9.3.1 FlowDocument viewer classes

WPF includes several controls for viewing flow content. You've already used the TextBlock for including small amounts of formatted text; now it's the turn of the classes for viewing whole documents.

There are three basic classes for viewing flow documents: FlowDocumentReader, FlowDocumentPageViewer, and FlowDocumentScrollViewer. They're all similar, but have slightly different features for different uses.

Figure 9.10 shows FlowDocumentExample.py,17 which embeds these three document viewers in a TabControl. They all show the same document so that you can see the differences.

Like the other WPF controls you've used, you can populate these readers directly from XAML or with an object tree created from code. For documents, it makes much more sense to use XAML, but there's a bit of work to be done from code. Let's look at how you use the viewers and what the differences are.

Wpf Flowdocumentreader Effect

Figure 9.10 The three flow document viewer classes displaying a flow content document

Available in the downloadable source code, under Chapter 9, from http://www.ironpythoninaction.com/.

Figure 9.10 The three flow document viewer classes displaying a flow content document

Available in the downloadable source code, under Chapter 9, from http://www.ironpythoninaction.com/.

THE FLOWDOCUMENTREADER

The FlowDocumentReader is the most capable of the viewer controls. It has features that allow the user to switch the display mode between single page, two pages side by side, and continuous scrolling. It also has find and magnification built-in. Listing 9.12 shows the basic code to read in a XAML file and create the viewer.

Listing 9.12 Creating FlowDocumentReader from System.IO import File from System.Windows.Controls import (

FlowDocumentReader, FlowDocumentReaderViewingMode

from System.Windows.Markup import XamlReader xamlStream = File.OpenRead("FlowDocument.xaml") reader = FlowDocumentReader() flowDocument = XamlReader.Load(xamlStream) reader.Document = flowDocument reader.ViewingMode = FlowDocumentReaderViewingMode.Page

Because the viewers are controls, they live in the System.Windows.Controls namespace.

The default viewing mode is Page; so, strictly speaking, the last line of the code is unnecessary, but it demonstrates how you configure the viewing mode. The other enumeration members, for the alternative viewing modes, are Scroll and TwoPage. THE FLOWDOCUMENTPAGEVIEWER AND FLOWDOCUMENTSCROLLVIEWER

The other two viewer controls are used in exactly the same way, but present the document in a fixed viewing mode instead of allowing the user to choose. In consequence, they're lighter weight and should be used when different viewing modes aren't needed.

The FlowDocumentPageViewer shows documents in page-at-a-time mode and includes the magnification toolbar.

The FlowDocumentScrollViewer shows documents in single-page view mode, with a vertical scrollbar. A horizontal scrollbar is only shown if necessary. By default, this viewer has no toolbar, but you can switch it on by setting the IsToolBarVisible property to True.

So what sort of markup do you use to create flow documents?

9.3.2 Flow document markup

You've already seen a simple example of the XAML flow document markup when creating the TextBlock. But that only scratches the surface of what's possible.

The document displayed by FlowDocumentExample.py is FlowDocument.xaml. This shows off most of the document markup available. BASIC MARKUP

When creating a viewer control, you need to set the Document property with a Flow-Document, which must be the top-level element of the document. The basic markup elements of flow content are easy to use. This short document contains text in a paragraph, with bold and italic sections and a line break.

<FlowDocument xmlns="... > <Paragraph>

The markup enables me to make some things <Bold>Bold</Bold>, and other things <Italic>Italic</Italic>. Although it is another markup to learn, it is much easier to construct documents like this than to do it from code. <LineBreak />

This text follows a line break. </Paragraph> </FlowDocument>

All these elements have corresponding classes in the System.Windows.Documents namespace, but constructing a document from code would be extremely tedious.

As well as for dividing documents into paragraphs, you can use sections as container elements. They can be useful ways of forcing a page break or applying styling attributes to all contained elements. Children of a section must be block-level elements, which include the following:

■ BlockUIContainer

Following are two section declarations. The first ensures a page break; the second adds a light blue background to everything within the section.

<Section BreakPageBefore="True"/> <Section Background="LightBlue"> </Section>

Lists are also easy to construct.

<ListItem>

<Paragraph><Italic>Item Number 1 - Italic</Italic></Paragraph> </ListItem> <ListItem>

<Paragraph><Underline>Item Number 2 - Underline</Underline></Paragraph> </ListItem> </List>

Flow content can contain many other elements including figures, typography elements, and embedded user interfaces. You can find examples of these in FlowDocu-ment.xaml. We haven't yet covered images and hyperlinks because both of these elements have problems that we need to solve.

9.3.3 Document XAML and object tree processing

XAML is essentially an XML tree used to represent documents and user interfaces. If you need to apply transformations or changes to the tree, you have two choices. You can either process the XAML before loading, or apply changes directly to the object tree after loading. So far, we've encountered two reasons why you might want to do that. The first is to resolve the difficulty with specifying the location of images.

HANDLING IMAGES

For an image element to be properly displayed, you need to provide an absolute location for the image file. This is inconvenient, and it would be much better to specify the location relative to the document file. We've already solved this problem when creating images from code, but you need to do something different when working with XAML.

Because we're working with an XML tree, you could use an XML parser or apply XSLT transformations. But for specifying a location on the filesystem relative to the document, all you need is a placeholder representing the directory of the document. You can then do a string replace to insert that location into the XAML at runtime. Because you haven't yet used regular expressions (regex), we create a simple regex to do the job.

For regular expressions, you have the choice of working with the .NET or the Python libraries. Because we're more familiar with Python regular expressions, we use Python's re module18 (listing 9.13)

Listing 9.13 Regular expression to insert image locations into XAML at runtime from os.path import abspath, dirname, join import re from System. IO import File document_location = re . compile(r'<%\sdocument_location\s%> ' re.IGNORECASE) <-

documentDirectory = abspath(dirname(_file_)) <-

filename = join(documentDirectory, " FlowDocument.xaml") rawXAML = File.ReadAllText(filename)

Creates compiled regular expression object

Directory containing document if not documentDirectory.endswith('\\'):

documentDirectory += ' \\' I Performs xaml = re . sub (document_location, documentDirectory, rawXAML) <1- substitution

This code takes an image tag, <Image Source="<% document_location %>image2.jpg" />, and replaces <% document_location %> with the path to the directory containing the document. The advantage of using a regular expression is that the replacement can be case insensitive and also insensitive to whitespace inside the tag.

This leaves a further problem. You now have the XAML, with the correct image paths, as a string, but the XamlReader expects a stream. You get around this problem by using a MemoryStream to wrap the string (listing 9.14).

Listing 9.14 Wrapping a string as a stream from System.IO import MemoryStream from System.Text import Encoding bytes = Encoding.UTF8.GetBytes(xaml) stream = MemoryStream(bytes)

flowDocument = XamlReader.Load(stream)

See http://docs.python.org/lib/module-re.html.

XAML files must be in the UTF-8 encoding—which is why you use Encoding.UTF8 to get a byte array from the XAML string.

The next problem we have to solve is working with links in documents. HANDLING LINKS

Constructing hyperlinks in XAML is easy; but, unfortunately, they don't work quite how you might expect.

<Hyperlink NavigateUri="http://example.com">A Link</Hyperlink>

The NavigateUri isn't intended for linking to arbitrary URLs, but for navigating to XAML files. If these were created with code-behind, then the associated code would be loaded with the XAML, and you could construct Page19-based user interfaces (for both desktop applications and WPF applications hosted in a browser) that navigate like a web application. Because you can't use code-behind with IronPython, it isn't useful for our purposes, and hyperlinks in our XAML documents don't do anything.

But you can load the XAML and then find all the links in the object tree. You can then attach click handlers to the links that launch the default system browser with the URL specified in the NavigateUri.

To do this, you need a way of walking the WPF object tree after you've created it. Fortunately, there's a convenient helper class for doing this. Listing 9.15 shows a recursive generator function that uses LogicalTreeHelper to extract a list of objects. You specify the objects you're interested in by name, and FindChildren examines the class name of all the objects in the WPF tree to find them. Using a generator means that you don't have to build up a list, but can yield matching objects as you find them.

Listing 9.15 Attaching click handlers to all hyperlinks in a document from System.Windows import LogicalTreeHelper from System.Diagnostics import Process def FindChildren(child, name):

yield child for item in LogicalTreeHelper.GetChildren(child): if isinstance(item, basestring): <

continue for entry in FindChildren(item, name): yield entry <-

Ignores children that are strings flowDocument = XamlReader.Load(stream) def OnClick(sender, event): uri = sender.NavigateUri

Process.Start(uri.AbsoluteUri) <-

for link in FindChildren(tree, 'Hyperlink'): link.Click += OnClick

Returns children of object passed in

Launches browser to hyperlink NavigateUri

It would be simple to evolve this system to automatically hook up user interface elements embedded in documents. You could create a declarative naming scheme and

See http://msdn2.microsoft.com/en-us/library/system.windows.controls.page.aspx.

hook events up to handlers based on names declared in XAML elements. For example, an object named Click_onClick would have its Click event hooked up to the onClick method.

Although this section has focused on using LogicalTreeHelper in the context of documents, there's no need to restrict its use to this. XAML is just text, and Python is a particularly good language for text processing; even if you prefer working with code, the possibilities for dynamically generating XAML are interesting (especially for complex effects like animations and transitions).

0 0

Post a comment