Documents in WPF
Windows Presentation Foundation (WPF) offers a wide range of document features that enable the creation of high-fidelity content that is designed to be more easily accessed and read than in previous generations of Windows. In addition to enhanced capabilities and quality, WPF also provides integrated services for document display, packaging, and security. This topic provides an introduction to WPF document types and document packaging.
Types of Documents
WPF divides documents into two broad categories based on their intended use; these document categories are termed "fixed documents" and "flow documents."
Fixed documents are intended for applications that require a precise "what you see is what you get" (WYSIWYG) presentation, independent of the display or printer hardware used. Typical uses for fixed documents include desktop publishing, word processing, and form layout, where adherence to the original page design is critical. As part of its layout, a fixed document maintains the precise positional placement of content elements independent of the display or print device in use. For example, a fixed document page viewed on 96 dpi display will appear exactly the same when it is output to a 600 dpi laser printer as when it is output to a 4800 dpi phototypesetter. The page layout remains the same in all cases, while the document quality maximizes to the capabilities of each device.
By comparison, flow documents are designed to optimize viewing and readability and are best utilized when ease of reading is the primary document consumption scenario. Rather than being set to one predefined layout, flow documents dynamically adjust and reflow their content based on run-time variables such as window size, device resolution, and optional user preferences. A Web page is a simple example of a flow document where the page content is dynamically formatted to fit the current window. Flow documents optimize the viewing and reading experience for the user, based on the runtime environment. For example, the same flow document will dynamically reformat for optimal readability on either high-resolution 19-inch display or a small 2x3-inch PDA screen. In addition, flow documents have a number of built in features including search, viewing modes that optimize readability, and the ability to change the size and appearance of fonts. See Flow Document Overview for illustrations, examples, and in-depth information on flow documents.
Document Controls and Text Layout
The .NET Framework provides a set of pre-built controls that simplify using fixed documents, flow documents, and general text within your application. The display of fixed document content is supported using the DocumentViewer control. Display of flow document content is supported by three different controls: FlowDocumentReader, FlowDocumentPageViewer, and FlowDocumentScrollViewer which map to different user scenarios (see sections below). Other WPF controls provide simplified layout to support general text uses (see Text in the User Interface, below).
Fixed Document Control - DocumentViewer
The DocumentViewer control is designed to display FixedDocument content. The DocumentViewer control provides an intuitive user interface that provides built-in support for common operations including print output, copy to clipboard, zoom, and text search features. The control provides access to pages of content through a familiar scrolling mechanism. Like all WPF controls, DocumentViewer supports complete or partial restyling, which enables the control to be visually integrated into virtually any application or environment.
DocumentViewer is designed to display content in a read-only manner; editing or modification of content is not available and is not supported.
Flow Document Controls
Note: For more detailed information on flow document features and how to create them, see Flow Document Overview.
Display of flow document content is supported by three controls: FlowDocumentReader, FlowDocumentPageViewer, and FlowDocumentScrollViewer.
FlowDocumentReader
FlowDocumentReader includes features that enable the user to dynamically choose between various viewing modes, including a single-page (page-at-a-time) viewing mode, a two-page-at-a-time (book reading format) viewing mode, and a continuous scrolling (bottomless) viewing mode. For more information about these viewing modes, see FlowDocumentReaderViewingMode. If you do not need the ability to dynamically switch between different viewing modes, FlowDocumentPageViewer and FlowDocumentScrollViewer provide lighter-weight flow content viewers that are fixed in a particular viewing mode.
FlowDocumentPageViewer and FlowDocumentScrollViewer
FlowDocumentPageViewer shows content in page-at-a-time viewing mode, while FlowDocumentScrollViewer shows content in continuous scrolling mode. Both FlowDocumentPageViewer and FlowDocumentScrollViewer are fixed to a particular viewing mode. Compare to FlowDocumentReader, which includes features that enable the user to dynamically choose between various viewing modes (as provided by the FlowDocumentReaderViewingMode enumeration), at the cost of being more resource intensive than FlowDocumentPageViewer or FlowDocumentScrollViewer.
By default, a vertical scrollbar is always shown, and a horizontal scrollbar becomes visible if needed. The default UI for FlowDocumentScrollViewer does not include a toolbar; however, the IsToolBarVisible property can be used to enable a built-in toolbar.
Text in the User Interface
Besides adding text to documents, text can obviously be used in application UI such as forms. WPF includes multiple controls for drawing text to the screen. Each control is targeted to a different scenario and has its own list of features and limitations. In general, the TextBlock element should be used when limited text support is required, such as a brief sentence in a user interface (UI). Label can be used when minimal text support is required. For more information, see TextBlock Overview.
Document Packaging
The System.IO.Packaging APIs provide an efficient means to organize application data, document content, and related resources in a single container that is simple to access, portable, and easy to distribute. A ZIP file is an example of a Package type capable of holding multiple objects as a single unit. The packaging APIs provide a default ZipPackage implementation designed using an Open Packaging Conventions standard with XML and ZIP file architecture. The WPF packaging APIs make it simple to create packages, and to store and access objects within them. An object stored in a Package is referred to as a PackagePart ("part"). Packages can also include signed digital certificates that can be used to identify the originator of a part and to validate that the contents of a package have not been modified. Packages also include a PackageRelationship feature that allows additional information to be added to a package or associated with specific parts without actually modifying the content of existing parts. Package services also support Microsoft Windows Rights Management (RM).
The WPF Package architecture serves as the foundation for a number of key technologies:
XPS documents conforming to the XML Paper Specification (XPS).
Microsoft Office "12" open XML format documents (.docx).
Custom storage formats for your own application design.
Based on the packaging APIs, an XpsDocument is specifically designed for storing WPF fixed content documents. An XpsDocument is a self-contained document that can be opened in a viewer, displayed in a DocumentViewer control, routed to a print spool, or output directly to an XPS-compatible printer.
The following sections provide additional information on the Package and XpsDocument APIs provided with WPF.
Package Components
The WPF packaging APIs allow application data and documents to be organized into a single portable unit. A ZIP file is one of the most common types of packages and is the default package type provided with WPF. Package itself is an abstract class from which ZipPackage is implemented using an open standard XML and ZIP file architecture. The Open method uses ZipPackage to create and use ZIP files by default. A package can contain three basic types of items:
Application content, data, documents, and resource files. | |
X.509 Certificate for identification, authentication and validation. | |
Added information related to the package or a specific part. |
PackageParts
A PackagePart ("part") is an abstract class that refers to an object stored in a Package. In a ZIP file, the package parts correspond to the individual files stored within the ZIP file. ZipPackagePart provides the default implementation for serializable objects stored in a ZipPackage. Like a file system, parts contained in the package are stored in hierarchical directory or "folder-style" organization. Using the WPF packaging APIs, applications can write, store, and read multiple PackagePart objects using a single ZIP file container.
PackageDigitalSignatures
For security, a PackageDigitalSignature ("digital signature") can be associated with parts within a package. A PackageDigitalSignatureincorporates a 509 that provides two features:
Identifies and authenticates the originator of the part.
Validates that the part has not been modified.
The digital signature does not preclude a part from being modified, but a validation check against the digital signature will fail if the part is altered in any way. The application can then take appropriate action—for example, block opening the part or notify the user that the part has been modified and is not secure.
PackageRelationships
A PackageRelationship ("relationship") provides a mechanism for associating additional information with the package or a part within the package. A relationship is a package-level facility that can associate additional information with a part without modifying the actual part content. Inserting new data directly into the part content of is usually not practical in many cases:
The actual type of the part and its content schema is not known.
Even if known, the content schema might not provide a means for adding new information.
The part might be digitally signed or encrypted, precluding any modification.
Package relationships provide a discoverable means for adding and associating additional information with individual parts or with the entire package. Package relationships are used for two primary functions:
Defining dependency relationships from one part to another part.
Defining information relationships that add notes or other data related to the part.
A PackageRelationship provides a quick, discoverable means to define dependencies and add other information associated with a part of the package or the package as a whole.
Dependency Relationships
Dependency relationships are used to describe dependencies that one part makes to other parts. For example, a package might contain an HTML part that includes one or more <img> image tags. The image tags refer to images that are located either as other parts internal to the package or external to the package (such as accessible over the Internet). Creating a PackageRelationship associated with HTML file makes discovering and accessing the dependent resources quick and easy. A browser or viewer application can directly access the part relationships and immediately begin assembling the dependent resources without knowing the schema or parsing the document.
Information Relationships
Similar to a note or annotation, a PackageRelationship can also be used to store other types of information to be associated with a part without having to actually modify the part content itself.
XPS Documents
XML Paper Specification (XPS) document is a package that contains one or more fixed-documents along with all the resources and information required for rendering. XPS is also the native Windows Vista print spool file format. An XpsDocument is stored in standard ZIP dataset, and can include a combination of XML and binary components, such as image and font files. PackageRelationships are used to define the dependencies between the content and the resources required to fully render the document. The XpsDocument design provides a single, high-fidelity document solution that supports multiple uses:
Reading, writing, and storing fixed-document content and resources as a single, portable, and easy-to-distribute file.
Displaying documents with the XPS Viewer application.
Outputting documents in the native print spool output format of Windows Vista.
Routing documents directly to an XPS-compatible printer.
Document Serialization and Storage
Microsoft .NET Framework provides a powerful environment for creating and displaying high quality documents. Enhanced features that support both fixed-documents and flow-documents, advanced viewing controls, combined with powerful 2D and 3D graphic capabilities take .NET Framework applications to a new level of quality and user experience. Being able to flexibly manage an in-memory representation of a document is a key feature of .NET Framework, and being able to efficiently save and load documents from a data store is a need of almost every application. The process of converting a document from an internal in-memory representation to an external data store is termed serialization. The reverse process of reading a data store and recreating the original in-memory instance is termed deserialization.
About Document Serialization
Ideally the process of serializing and deserializing a document from and then back into memory is transparent to the application. The application calls a serializer "write" method to save the document, while a deserializer "read" method accesses the data store and recreates the original instance in memory. The specific format that the data is stored in is generally not a concern of the application as long as the serialize and deserialize process recreates the document back to its original form.
Applications often provide multiple serialization options which allow the user to save documents to different medium or to a different format. For example, an application might offer "Save As" options to store a document to a disk file, database, or web service. Similarly, different serializers could store the document in different formats such as in HTML, RTF, XML, XPS, or alternately to a third-party format. To the application, serialization defines an interface that isolates the details of the storage medium within the implementation of each specific serializer. In addition to the benefits of encapsulating storage details, the .NET Framework System.Windows.Documents.Serialization APIs provide several other important features.
Features of .NET Framework 3.0 Document Serializers
Direct access to the high-level document objects (logical tree and visuals) enable efficient storage of paginated content, 2D/3D elements, images, media, hyperlinks, annotations, and other support content.
Synchronous and asynchronous operation.
Support for plug-in serializers with enhanced capabilities:
System-wide access for use by all .NET Framework applications.
Simple application plug-in discoverability.
Simple deployment, installation, and update for custom third-party plug-ins.
User interface support for custom run-time settings and options.
XPS Print Path
The Microsoft .NET Framework XPS print path also provides an extensible mechanism for writing documents through print output. XPS serves as both a document file format and is the native print spool format for Windows Vista. XPS documents can be sent directly to XPS-compatible printers without the need for conversion to an intermediate format. See the Printing Overview for additional information on print path output options and capabilities.
Plug-in Serializers
The System.Windows.Documents.Serialization APIs provide support for both plug-in serializers and linked serializers that are installed separately from the application, bind at run time, and are accessed by using the SerializerProvider discovery mechanism. Plug-in serializers offer enhanced benefits for ease of deployment and system-wide use. Linked serializers can also be implemented for partial trust environments such as XAML browser applications (XBAPs) where plug-in serializers are not accessible. Linked serializers, which are based on a derived implementation of the SerializerWriter class, are compiled and linked directly into the application. Both plug-in serializers and linked serializers operate through identical public methods and events which make it easy to use either or both types of serializers in the same application.
Plug-in serializers aid application developers by providing extensibility to new storage designs and file formats without having to code directly for every potential format at build time. Plug-in serializers also benefit third-party developers by providing a standardized means to deploy, install, and update system accessible plug-ins for custom or proprietary file formats.
Using a Plug-in Serializer
Plug-in serializers are simple to use. The SerializerProvider class enumerates a SerializerDescriptor object for each plug-in installed on the system. The IsLoadable property filters the installed plug-ins based on the current configuration and verifies that the serializer can be loaded and used by the application. The SerializerDescriptor also provides other properties, such as DisplayName and DefaultFileExtension, which the application can use to prompt the user in selecting a serializer for an available output format. A default plug-in serializer for XPS is provided with .NET Framework and is always enumerated. After the user selects an output format, the CreateSerializerWriter method is used to create a SerializerWriter for the specific format. The SerializerWriter.Write method can then be called to output the document stream to the data store.
The following example illustrates an application that uses the SerializerProvider method in a "PlugInFileFilter" property. PlugInFileFilter enumerates the installed plug-ins and builds a filter string with the available file options for a SaveFileDialog.
// ------------------------ PlugInFileFilter -------------------------- /// <summary> /// Gets a filter string for installed plug-in serializers.</summary> /// <remark> /// PlugInFileFilter is used to set the SaveFileDialog or /// OpenFileDialog "Filter" property when saving or opening files /// using plug-in serializers.</remark> private string PlugInFileFilter { get { // Create a SerializerProvider for accessing plug-in serializers. SerializerProvider serializerProvider = new SerializerProvider(); string filter = ""; // For each loadable serializer, add its display // name and extension to the filter string. foreach (SerializerDescriptor serializerDescriptor in serializerProvider.InstalledSerializers) { if (serializerDescriptor.IsLoadable) { // After the first, separate entries with a "|". if (filter.Length > 0) filter += "|"; // Add an entry with the plug-in name and extension. filter += serializerDescriptor.DisplayName + " (*" + serializerDescriptor.DefaultFileExtension + ")|*" + serializerDescriptor.DefaultFileExtension; } } // Return the filter string of installed plug-in serializers. return filter; } }
After an output file name has been selected by the user, the following example illustrates use of the CreateSerializerWriter method to store a given document in a specified format.
// Create a SerializerProvider for accessing plug-in serializers. SerializerProvider serializerProvider = new SerializerProvider(); // Locate the serializer that matches the fileName extension. SerializerDescriptor selectedPlugIn = null; foreach ( SerializerDescriptor serializerDescriptor in serializerProvider.InstalledSerializers ) { if ( serializerDescriptor.IsLoadable && fileName.EndsWith(serializerDescriptor.DefaultFileExtension) ) { // The plug-in serializer and fileName extensions match. selectedPlugIn = serializerDescriptor; break; // foreach } } // If a match for a plug-in serializer was found, // use it to output and store the document. if (selectedPlugIn != null) { Stream package = File.Create(fileName); SerializerWriter serializerWriter = serializerProvider.CreateSerializerWriter(selectedPlugIn, package); IDocumentPaginatorSource idoc = flowDocument as IDocumentPaginatorSource; serializerWriter.Write(idoc.DocumentPaginator, null); package.Close(); return true; }
Installing Plug-in Serializers
The SerializerProvider class supplies the upper-level application interface for plug-in serializer discovery and access. SerializerProvider locates and provides the application a list of the serializers that are installed and accessible on the system. The specifics of the installed serializers are defined through registry settings. Plug-in serializers can be added to the registry by using the RegisterSerializer method; or if .NET Framework is not yet installed, the plug-in installation script can directly set the registry values itself. The UnregisterSerializer method can be used to remove a previously installed plug-in, or the registry settings can be reset similarly by an uninstall script.
Creating a Plug-in Serializer
Both plug-in serializers and linked serializers use the same exposed public methods and events, and similarly can be designed to operate either synchronously or asynchronously. There are three basic steps normally followed to create a plug-in serializer:
Implement and debug the serializer first as a linked serializer. Initially creating the serializer compiled and linked directly in a test application provides full access to breakpoints and other debug services helpful for testing.
After the serializer is fully tested, an ISerializerFactory interface is added to create a plug-in. The ISerializerFactory interface permits full access to all .NET Framework objects which includes the logical tree, UIElement objects, IDocumentPaginatorSource, and Visual elements. Additionally ISerializerFactory provides the same synchronous and asynchronous methods and events used by linked serializers. Since large documents can take time to output, asynchronous operations are recommended to maintain responsive user interaction and offer a "Cancel" option if some problem occurs with the data store.
After the plug-in serializer is created, an installation script is implemented for distributing and installing (and uninstalling) the plug-in (see above, "Installing Plug-in Serializers").
Annotations
Annotations Overview
Writing notes or comments on paper documents is such a commonplace activity that we almost take it for granted. These notes or comments are "annotations" that we add to a document to flag information or to highlight items of interest for later reference. Although writing notes on printed documents is easy and commonplace, the ability to add personal comments to electronic documents is typically very limited, if available at all.
This topic reviews several common types of annotations, specifically sticky notes and highlights, and illustrates how the Microsoft Annotations Framework facilitates these types of annotations in applications through the Windows Presentation Foundation (WPF) document viewing controls. WPF document viewing controls that support annotations include FlowDocumentReader and FlowDocumentScrollViewer, as well as controls derived from DocumentViewerBase such as DocumentViewer and FlowDocumentPageViewer.
Sticky Notes
A typical sticky note contains information written on a small piece of colored paper that is then "stuck" to a document. Digital sticky notes provide similar functionality for electronic documents, but with the added flexibility to include many other types of content such as typed text, handwritten notes (for example, Tablet PC "ink" strokes), or Web links.
The following illustration shows some examples of highlight, text sticky note, and ink sticky note annotations.
The following example shows the method that you can use to enable annotation support in your application.
// ------------------------ StartAnnotations -------------------------- /// <summary> /// Enables annotations and displays all that are viewable.</summary> private void StartAnnotations() { // If there is no AnnotationService yet, create one. if (_annotService == null) // docViewer is a document viewing control named in Window1.xaml. _annotService = new AnnotationService(docViewer); // If the AnnotationService is currently enabled, disable it. if (_annotService.IsEnabled == true) _annotService.Disable(); // Open a stream to the file for storing annotations. _annotStream = new FileStream( _annotStorePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); // Create an AnnotationStore using the file stream. _annotStore = new XmlStreamStore(_annotStream); // Enable the AnnotationService using the new store. _annotService.Enable(_annotStore); }// end:StartAnnotations()
Highlights
People use creative methods to draw attention to items of interest when they mark up a paper document, such as underlining, highlighting, circling words in a sentence, or drawing marks or notations in the margin. Highlight annotations in Microsoft Annotations Framework provide a similar feature for marking up information displayed in WPF document viewing controls.
The following illustration shows an example of a highlight annotation.
Users typically create annotations by first selecting some text or an item of interest, and then right-clicking to display a ContextMenu of annotation options. The following example shows the Extensible Application Markup Language (XAML) you can use to declare a ContextMenu with routed commands that users can access to create and manage annotations.
<DocumentViewer.ContextMenu> <ContextMenu> <MenuItem Command="ApplicationCommands.Copy" /> <Separator /> <!-- Add a Highlight annotation to a user selection. --> <MenuItem Command="ann:AnnotationService.CreateHighlightCommand" Header="Add Highlight" /> <!-- Add a Text Note annotation to a user selection. --> <MenuItem Command="ann:AnnotationService.CreateTextStickyNoteCommand" Header="Add Text Note" /> <!-- Add an Ink Note annotation to a user selection. --> <MenuItem Command="ann:AnnotationService.CreateInkStickyNoteCommand" Header="Add Ink Note" /> <Separator /> <!-- Remove Highlights from a user selection. --> <MenuItem Command="ann:AnnotationService.ClearHighlightsCommand" Header="Remove Highlights" /> <!-- Remove Text Notes and Ink Notes from a user selection. --> <MenuItem Command="ann:AnnotationService.DeleteStickyNotesCommand" Header="Remove Notes" /> <!-- Remove Highlights, Text Notes, Ink Notes from a selection. --> <MenuItem Command="ann:AnnotationService.DeleteAnnotationsCommand" Header="Remove Highlights & Notes" /> </ContextMenu> </DocumentViewer.ContextMenu>
Data Anchoring
The Annotations Framework binds annotations to the data that the user selects, not just to a position on the display view. Therefore, if the document view changes, such as when the user scrolls or resizes the display window, the annotation stays with the data selection to which it is bound. For example, the following graphic illustrates an annotation that the user has made on a text selection. When the document view changes (scrolls, resizes, scales, or otherwise moves), the highlight annotation moves with the original data selection.
Matching Annotations with Annotated Objects
You can match annotations with the corresponding annotated objects. For example, consider a simple document reader application that has a comments pane. The comments pane might be a list box that displays the text from a list of annotations that are anchored to a document. If the user selects an item in the list box, then the application brings into view the paragraph in the document that the corresponding annotation object is anchored to.
The following example demonstrates how to implement the event handler of such a list box that serves as the comments pane.
void annotationsListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { Annotation comment = (sender as ListBox).SelectedItem as Annotation; if (comment != null) { // IAnchorInfo info; // service is an AnnotationService object // comment is an Annotation object info = AnnotationHelper.GetAnchorInfo(this.service, comment); TextAnchor resolvedAnchor = info.ResolvedAnchor as TextAnchor; TextPointer textPointer = (TextPointer)resolvedAnchor.BoundingStart; textPointer.Paragraph.BringIntoView(); } }
Another example scenario involves applications that enable the exchange of annotations and sticky notes between document readers through e-mail. This feature enables these applications to navigate the reader to the page that contains the annotation that is being exchanged.
Annotations Schema
This topic describes the XML schema definition (XSD) used by the Microsoft Annotations Framework to save and retrieve user annotation data.
The Annotations Framework serializes annotation data from an internal representation to an XML format. The XML format used for this conversion is described by the Annotations Framework XSD Schema. The schema defines the implementation-independent XML format that can be used to exchange annotation data between applications.
The Annotations Framework XML schema definition consists of two subschemas
The Annotations XML Core Schema (Core Schema).
The Annotations XML Base Schema (Base Schema).
The Core Schema defines the primary XML structure of an Annotation. The majority of XML elements defined in the Core Schema correspond to types in the System.Windows.Annotations namespace. The Core Schema exposes three extension points where applications can add their own XML data. These extension points include the Authors, ContentLocatorPart, and "Content". (Content elements are provided in the form of an XmlElement list.)
The Base Schema described in this topic defines the extensions for the Authors, ContentLocatorPart, and Content types included with the initial Windows Presentation Foundation (WPF) release.
This topic contains the following sections:
Annotations XML Core Schema
The Annotations XML Core Schema defines the XML structure that is used to store Annotation objects.
<xsd:schema elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://schemas.microsoft.com/windows/annotations/2003/11/core" xmlns:anc="http://schemas.microsoft.com/windows/annotations/2003/11/core"> <!-- The Annotations element groups a number of annotations. --> <xsd:element name="Annotations" type="anc:AnnotationsType" /> <xsd:complexType name="AnnotationsType"> <xsd:sequence> <xsd:element name="Annotation" minOccurs="0" maxOccurs="unbounded" type="anc:AnnotationType" /> </xsd:sequence> </xsd:complexType> <!-- AnnotationType defines the structure of the Annotation element. --> <xsd:complexType name="AnnotationType"> <xsd:sequence> <!-- List of 0 or more authors. --> <xsd:element name="Authors" minOccurs="0" maxOccurs="1" type="anc:AuthorListType" /> <!-- List of 0 or more anchors. --> <xsd:element name="Anchors" minOccurs="0" maxOccurs="1" type="anc:ResourceListType" /> <!-- List of 0 or more cargos. --> <xsd:element name="Cargos" minOccurs="0" maxOccurs="1" type="anc:ResourceListType" /> </xsd:sequence> <!-- Unique annotation ID. --> <xsd:attribute name="Id" type="xsd:string" use="required" /> <!-- Annotation "Type" is used to map the annotation to an annotation component that takes care of the visual representation of the annotation. WPF V1 recognizes three annotation types: http://schemas.microsoft.com/windows/annotations/2003/11/base:Highlight http://schemas.microsoft.com/windows/annotations/2003/11/base:TextStickyNote http://schemas.microsoft.com/windows/annotations/2003/11/base:InkStickyNote --> <xsd:attribute name="Type" type="xsd:QName" use="required" /> <!-- Time when the annotation was last modified. --> <xsd:attribute name="LastModificationTime" use="optional" type="xsd:dateTime" /> <!-- Time when the annotation was created. --> <xsd:attribute name="CreationTime" use="optional" type="xsd:dateTime" /> </xsd:complexType> <!-- "Authors" consists of 0 or more elements that represent an author. --> <xsd:complexType name="AuthorListType"> <xsd:sequence minOccurs="0" maxOccurs="unbounded"> <xsd:element ref="anc:Author" /> </xsd:sequence> </xsd:complexType> <!-- The core schema allows any author type. Supported author types in version 1 (V1) are described in the base schema. --> <xsd:element name="Author" abstract="true" block="extension restriction"/> <!-- Both annotation anchor and annotation cargo are represented by the ResourceListType which contains 0 or more "Resource" elements. --> <xsd:complexType name="ResourceListType"> <xsd:sequence> <xsd:element name="Resource" minOccurs="0" maxOccurs="unbounded" type="anc:ResourceType" /> </xsd:sequence> </xsd:complexType> <!-- Resource groups identification, location and/or content of some information. --> <xsd:complexType name="ResourceType"> <xsd:choice minOccurs="0" maxOccurs="unbounded" > <xsd:choice> <xsd:element name="ContentLocator" type="anc:ContentLocatorType" /> <xsd:element name="ContentLocatorGroup" type="anc:ContentLocatorGroupType" /> </xsd:choice> <xsd:element ref="anc:Content"/> </xsd:choice> <!-- Unique resource identifier. --> <xsd:attribute name="Id" type="xsd:string" use="required" /> <!-- Optional resource name. --> <xsd:attribute name="Name" type="xsd:string" use="optional" /> </xsd:complexType> <!-- ContentLocatorGroup contains a set of ContentLocators --> <xsd:complexType name="ContentLocatorGroupType"> <xsd:sequence> <xsd:element name="ContentLocator" minOccurs="1" maxOccurs="unbounded" type="anc:ContentLocatorType" /> </xsd:sequence> </xsd:complexType> <!-- A ContentLocator describes the location or the identification of particular data within some context. The ContentLocator consists of one or more ContentLocatorParts. Each ContentLocatorPart needs to be successively applied to the context to arrive at the data. What "applying", "context", and "data" mean is application dependent. --> <xsd:complexType name="ContentLocatorType"> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element ref="anc:ContentLocatorPart" /> </xsd:sequence> </xsd:complexType> <!-- A ContentLocatorPart is a set of "Item" elements. Each "Item" element has "Name" and "Value" attributes that define a name/value pair. ContentLocatorPart is an abstract type that must be restricted for each concrete ContentLocatorPart definition. This restriction should define allowed names and values for the concrete ContentLocatorPart type. That way the application can define its own way of locating information. The ContentLocatorPartTypes that are allowed in version 1 (V1) of WPF are defined in the Annotations Base Schema. --> <xsd:element name="ContentLocatorPart" type="anc:ContentLocatorPartType" abstract="true" /> <xsd:complexType name="ContentLocatorPartType" abstract="true" block="restriction"> <xsd:sequence minOccurs="0" maxOccurs="unbounded"> <xsd:element name="Item" type="anc:ItemType" /> </xsd:sequence> </xsd:complexType> <xsd:complexType name="ItemType" abstract="true" > <xsd:attribute name="Name" type='xsd:string' use="required" /> <xsd:attribute name="Value" type='xsd:string' use="optional" /> </xsd:complexType> <!-- Content describes the underlying content of a resource. This is an abstract type that should be redefined for each concrete content type through restriction. Allowed content types in WPF version 1 are defined in the Annotations Base Schema. --> <xsd:element name="Content" abstract="true" block="extension restriction"/> </xsd:schema>
Annotations XML Base Schema
The Base Schema defines the XML structure for the three abstract elements defined in the Core Schema – Authors, ContentLocatorPart, and Contents.
<xsd:schema elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://schemas.microsoft.com/windows/annotations/2003/11/base" xmlns:anb="http://schemas.microsoft.com/windows/annotations/2003/11/base" xmlns:anc="http://schemas.microsoft.com/windows/annotations/2003/11/core"> <xsd:import schemaLocation="AnnotationCoreV1.xsd" namespace="http://schemas.microsoft.com/windows/annotations/2003/11/core"/> <!-- ***** Author ***** --> <!-- Simple DisplayName Author --> <xsd:complexType name="StringAuthorType"> <xsd:simpleContent > <xsd:extension base='xsd:string' /> </xsd:simpleContent> </xsd:complexType> <xsd:element name="StringAuthor" type="anb:StringAuthorType" substitutionGroup="anc:Author"/> <!-- ***** LocatorParts ***** --> <!-- Helper types --> <!-- CountItemNameType - helper type to define count item --> <xsd:simpleType name="CountItemNameType"> <xsd:restriction base='xsd:string'> <xsd:pattern value="Count" /> </xsd:restriction> </xsd:simpleType> <!-- NumberType - helper type to define segment count item --> <xsd:simpleType name="NumberType"> <xsd:restriction base='xsd:string'> <xsd:pattern value="\d*" /> </xsd:restriction> </xsd:simpleType> <!-- SegmentNameType: helper type to define possible segment name types --> <xsd:simpleType name="SegmentItemNameType"> <xsd:restriction base='xsd:string'> <xsd:pattern value="Segment\d*" /> </xsd:restriction> </xsd:simpleType> <!-- Flow Locator Part --> <!-- FlowSegmentValueItemType: helper type to define flow segment values --> <xsd:simpleType name="FlowSegmentItemValueType"> <xsd:restriction base='xsd:string'> <xsd:pattern value=" \d*,\d*" /> </xsd:restriction> </xsd:simpleType> <!-- FlowItemType --> <xsd:complexType name="FlowItemType" abstract = "true"> <xsd:complexContent> <xsd:restriction base="anc:ItemType"> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- FlowSegmentItemType --> <xsd:complexType name="FlowSegmentItemType"> <xsd:complexContent> <xsd:restriction base="anb:FlowItemType"> <xsd:attribute name="Name" use="required" type="anb:SegmentItemNameType"/> <xsd:attribute name="Value" use="required" type="anb:FlowSegmentItemValueType"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- FlowCountItemType --> <xsd:complexType name="FlowCountItemType"> <xsd:complexContent> <xsd:restriction base="anb:FlowItemType"> <xsd:attribute name="Name" type="anb:CountItemNameType" use="required"/> <xsd:attribute name="Value" type="anb:NumberType" use="required"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- CharacterRangeType is an extension of ContentLocatorPartType that locates * part of the content within a FlowDocument. CharacterRangeType contains one * "Item" element with name "Count" and value the number(N) of "SegmentXX" * elements that this ContentLocatorPart has. It also contains N "Item" * elements with name "SegmentXX" where XX is a number from 0 to N-1. The * value of each "SegmnetXX" element is a string in the form "offset, length" * which locates one sequence of symbols in the FlowDocument. Example: * <anb:CharacterRange> * <anc:Item Name="Count" Value="2" /> * <anc:Item Name="Segment0" Value="5,10" /> * <anc:Item Name="Segment1" Value="25,2" /> * </anb:ChacterRange> --> <xsd:complexType name="CharacterRangeType"> <xsd:complexContent> <xsd:extension base="anc:ContentLocatorPartType"> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element name="Item" type="anb:FlowItemType" /> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> <!-- CharacterRange element substitutes ContentLocatorPart element --> <xsd:element name="CharacterRange" type="anb:CharacterRangeType" substitutionGroup="anc:ContentLocatorPart"/> <!-- Fixed LocatorPart --> <!-- Helper type – FixedItemType --> <xsd:complexType name="FixedItemType" abstract = "true"> <xsd:complexContent> <xsd:restriction base="anc:ItemType"> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- Helper type – FixedCountItemType: ContentLocatorPart items count --> <xsd:complexType name="FixedCountItemType"> <xsd:complexContent> <xsd:restriction base="anb:FixedItemType"> <xsd:attribute name="Name" type="anb:CountItemNameType" use="required"/> <xsd:attribute name="Value" type="anb:NumberType" use="required"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- Helper type -FixedSegmentValue: Defines possible fixed segment values --> <xsd:simpleType name="FixedSegmentItemValueType"> <xsd:restriction base='xsd:string'> <xsd:pattern value="\d*,\d*,\d*,\d*" /> </xsd:restriction> </xsd:simpleType> <!-- Helper type - FixedSegmentItemType --> <xsd:complexType name="FixedSegmentItemType"> <xsd:complexContent> <xsd:restriction base="anb:FixedItemType"> <xsd:attribute name="Name" use="required" type="anb:SegmentItemNameType"/> <xsd:attribute name="Value" use="required" type="anb:FixedSegmentItemValueType "/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <!-- FixedTextRangeType is an extension of ContentLocatorPartType that locates * content within a FixedDocument. It contains one "Item" element with name * "Count" and value the number (N) of "Item" elements with name "SegmentXX" * that this ContentLocatorPart has. FixedTextRange locator part also * contains N "Item" elements with one attribute Name="SegmentXX" where XX is * a number from 0 to N-1 and one attribute "Value" in the form "X1, Y1, X2, * Y2". Here X1,Y1 are the coordinates of the start symbol in this segment, * X2,Y2 are the coordinates of the end symbol in this segment. Example: * * <anb:FixedTextRange> * <anc:Item Name="Count" Value="2" /> * <anc:Item Name="Segment0" Value="10,5,20,5" /> * <anc:Item Name="Segment1" Value="25,15, 25,20" /> * </anb:FixedTextRange> --> <xsd:complexType name="FixedTextRangeType"> <xsd:complexContent> <xsd:extension base="anc:ContentLocatorPartType"> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element name="Item" type="anb:FixedItemType" /> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> <!-- FixedTextRange element substitutes ContentLocatorPart element --> <xsd:element name="FixedTextRange" type="anb:FixedTextRangeType" substitutionGroup="anc:ContentLocatorPart"/> <!-- DataId --> <!-- ValueItemNameType: helper type to define value item --> <xsd:simpleType name="ValueItemNameType"> <xsd:restriction base='xsd:string'> <xsd:pattern value="Value" /> </xsd:restriction> </xsd:simpleType> <!-- StringValueItemType --> <xsd:complexType name="StringValueItemType"> <xsd:complexContent> <xsd:restriction base="anc:ItemType"> <xsd:attribute name="Name" type="anb:ValueItemNameType" use="required"/> <xsd:attribute name="Value" type="xsd:string" use="required"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="StringValueLocatorPartType"> <xsd:complexContent> <xsd:extension base="anc:ContentLocatorPartType"> <xsd:sequence minOccurs="1" maxOccurs="1"> <xsd:element name="Item" type="anb:ValueItemType" /> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> <!-- DataId element substitutes ContentLocatorPart and is used to locate a * subtree in the logical tree. Including DataId locator part in a * ContentLocator helps to narrow down the search for a particular content. * Examle of DataId ContentLocatorPart: * * <anb:DataId> * <anc:Item Name="Value" Value="FlowDocument" /> * </anb:DataId> --> <xsd:element name="DataId" type="anb: StringValueLocatorPartType " substitutionGroup="anc:ContentLocatorPart"/> <!-- PageNumber --> <!-- NumberValueItemType --> <xsd:complexType name="NumberValueItemType"> <xsd:complexContent> <xsd:restriction base="anc:ItemType"> <xsd:attribute name="Name" type="anb:ValueItemNameType" use="required"/> <xsd:attribute name="Value" type="anb:NumberType" use="required"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="NumberValueLocatorPartType"> <xsd:complexContent> <xsd:extension base="anc:ContentLocatorPartType"> <xsd:sequence minOccurs="1" maxOccurs="1"> <xsd:element name="Item" type="anb:ValueItemType" /> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> <-- PageNumber element substitutes ContentLocatorPart and is used to locate a * page in a FixedDocument. PageNumber ContentLocatorPart is used in * conjunction with the FixedTextRange ContentLocatorPart and it shows on with * page are the coordinates defined in the FixedTextRange. * Example of a PageNumber ContentLocatorPart: * * <anb:PageNumber> * <anc:Item Name="Value" Value="1" /> * </anb:PageNumber> --> <xsd:element name="PageNumber" type="anb:NumbnerValueLocatorPartType" substitutionGroup="anc:ContentLocatorPart"/> <!-- ***** Content ***** --> <!-- Highlight colors – defines highlight color for annotations of type * Highlight or normal and active anchor colors for annotations of type * TextStickyNote and InkStickyNote. --> <xsd:complexType name="ColorsContentType"> <xsd:attribute name="Background" type='xsd:string' use="required" /> <xsd:attribute name="ActiveBackground" type='xsd:string' use="optional" /> </xsd:complexType> <xsd:element name="Colors" type="anb:ColorsContentType" substitutionGroup="anc:Content"/> <!-- RTB Text –contains XAML representing StickyNote Reach Text Box text. * Used in annotations of type TextStickyNote. --> <xsd:complexType name="TextContentType"> <!-- See XAML schema for RTB content --> </xsd:complexType> <xsd:element name="Text" type="anb:TextContentType" substitutionGroup="anc:Content"/> <-- Ink – contains XAML representing Sticky Note ink. * Used in annotations of type InkStickyNote. --> <xsd:complexType name="InkContentType"> <!-- See XAML schema for Ink content --> </xsd:complexType> <xsd:element name="Ink" type="anb:InkContentType" substitutionGroup="anc:Content"/> <!-- SN Metadata – defines StickyNote attributes as position width, height, * etc. Used in annotations of type TextStickyNote and InkStickyNote. --> <xsd:complexType name="MetadataContentType"> <xsd:attribute name="Left" type='xsd:decimal' use="optional" /> <xsd:attribute name="Top" type='xsd:decimal' use="optional" /> <xsd:attribute name="Width" type='xsd:decimal' use="optional" /> <xsd:attribute name="Height" type='xsd:decimal' use="optional" /> <xsd:attribute name="XOffset" type='xsd:decimal' use="optional" /> <xsd:attribute name="YOffset" type='xsd:decimal' use="optional" /> <xsd:attribute name="ZOrder" type='xsd:decimal' use="optional" /> </xsd:complexType> <xsd:element name="Metadata" type="anb:MetadataContentType" substitutionGroup="anc:Content"/> </xsd:schema>
Sample XML Produced by Annotations XmlStreamStore
The XML that follows shows the output of an Annotations XmlStreamStore and the organization of a sample file that contains three annotations - a highlight, a text sticky-note, and an ink stick-note.
<?xml version="1.0" encoding="utf-8"?> <anc:Annotations xmlns:anc="http://schemas.microsoft.com/windows/annotations/2003/11/core" xmlns:anb="http://schemas.microsoft.com/windows/annotations/2003/11/base"> <anc:Annotation Id="d308ea9b-36eb-4cc4-94d0-97634f10f7a2" CreationTime="2006-09-13T18:28:51.4465702-07:00" LastModificationTime="2006-09-13T18:28:51.4465702-07:00" Type="anb:Highlight"> <anc:Anchors> <anc:Resource Id="4f53661b-7328-4673-8e3f-c53f08b9cd94"> <anc:ContentLocator> <anb:DataId> <anc:Item Name="Value" Value="FlowDocument" /> </anb:DataId> <anb:CharacterRange> <anc:Item Name="Segment0" Value="600,609" /> <anc:Item Name="Count" Value="1" /> </anb:CharacterRange> </anc:ContentLocator> </anc:Resource> </anc:Anchors> </anc:Annotation> <anc:Annotation Id="d7a8d271-387e-4144-9f8b-bc3c97816e5f" CreationTime="2006-09-13T18:28:56.7903202-07:00" LastModificationTime="2006-09-13T18:28:56.8996952-07:00" Type="anb:TextStickyNote"> <anc:Authors> <anb:StringAuthor>Denise Smith</anb:StringAuthor> </anc:Authors> <anc:Anchors> <anc:Resource Id="dab2560e-6ebd-4ad0-80f9-483356a3be0b"> <anc:ContentLocator> <anb:DataId> <anc:Item Name="Value" Value="FlowDocument" /> </anb:DataId> <anb:CharacterRange> <anc:Item Name="Segment0" Value="787,801" /> <anc:Item Name="Count" Value="1" /> </anb:CharacterRange> </anc:ContentLocator> </anc:Resource> </anc:Anchors> <anc:Cargos> <anc:Resource Id="ea4dbabd-b400-4cf9-8908-5716b410f9e4" Name="Meta Data"> <anb:MetaData anb:ZOrder="0" /> </anc:Resource> </anc:Cargos> </anc:Annotation> <anc:Annotation Id="66803c69-b0d7-4cc3-bdff-cacc1955e806" CreationTime="2006-09-13T18:29:03.6653202-07:00" LastModificationTime="2006-09-13T18:29:03.7121952-07:00" Type="anb:InkStickyNote"> <anc:Authors> <anb:StringAuthor>Mike Nash</anb:StringAuthor> </anc:Authors> <anc:Anchors> <anc:Resource Id="52251c53-8eeb-4fd7-b8f3-94e78dfc25fa"> <anc:ContentLocator> <anb:DataId> <anc:Item Name="Value" Value="FlowDocument" /> </anb:DataId> <anb:CharacterRange> <anc:Item Name="Segment0" Value="880,884" /> <anc:Item Name="Count" Value="1" /> </anb:CharacterRange> </anc:ContentLocator> </anc:Resource> </anc:Anchors> <anc:Cargos> <anc:Resource Id="11e50b97-8d91-4ff9-82c3-16607b2b552b" Name="Meta Data"> <anb:MetaData anb:ZOrder="1" /> </anc:Resource> </anc:Cargos> </anc:Annotation> </anc:Annotations>
Flow Content
Flow Document Overview
Flow documents are designed to optimize viewing and readability. Rather than being set to one predefined layout, flow documents dynamically adjust and reflow their content based on run-time variables such as window size, device resolution, and optional user preferences. In addition, flow documents offer advanced document features, such as pagination and columns. This topic provides an overview of flow documents and how to create them.
What is a Flow Document
A flow document is designed to "reflow content" depending on window size, device resolution, and other environment variables. In addition, flow documents have a number of built in features including search, viewing modes that optimize readability, and the ability to change the size and appearance of fonts. Flow Documents are best utilized when ease of reading is the primary document consumption scenario. In contrast, Fixed Documents are designed to have a static presentation. Fixed Documents are useful when fidelity of the source content is essential. See Documents in WPF for more information on different types of documents.
The following illustration shows a sample flow document viewed in several windows of different sizes. As the display area changes, the content reflows to make the best use of the available space.
As seen in the image above, flow content can include many components including paragraphs, lists, images, and more. These components correspond to elements in markup and objects in procedural code. We will go over these classes in detail later in the Flow Related Classes section of this overview. For now, here is a simple code example that creates a flow document consisting of a paragraph with some bold text and a list .
<!-- This simple flow document includes a paragraph with some bold text in it and a list. --> <FlowDocumentReader xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <FlowDocument> <Paragraph> <Bold>Some bold text in the paragraph.</Bold> Some text that is not bold. </Paragraph> <List> <ListItem> <Paragraph>ListItem 1</Paragraph> </ListItem> <ListItem> <Paragraph>ListItem 2</Paragraph> </ListItem> <ListItem> <Paragraph>ListItem 3</Paragraph> </ListItem> </List> </FlowDocument> </FlowDocumentReader>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class SimpleFlowExample : Page { public SimpleFlowExample() { Paragraph myParagraph = new Paragraph(); // Add some Bold text to the paragraph myParagraph.Inlines.Add(new Bold(new Run("Some bold text in the paragraph."))); // Add some plain text to the paragraph myParagraph.Inlines.Add(new Run(" Some text that is not bold.")); // Create a List and populate with three list items. List myList = new List(); // First create paragraphs to go into the list item. Paragraph paragraphListItem1 = new Paragraph(new Run("ListItem 1")); Paragraph paragraphListItem2 = new Paragraph(new Run("ListItem 2")); Paragraph paragraphListItem3 = new Paragraph(new Run("ListItem 3")); // Add ListItems with paragraphs in them. myList.ListItems.Add(new ListItem(paragraphListItem1)); myList.ListItems.Add(new ListItem(paragraphListItem2)); myList.ListItems.Add(new ListItem(paragraphListItem3)); // Create a FlowDocument with the paragraph and list. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(myParagraph); myFlowDocument.Blocks.Add(myList); // Add the FlowDocument to a FlowDocumentReader Control FlowDocumentReader myFlowDocumentReader = new FlowDocumentReader(); myFlowDocumentReader.Document = myFlowDocument; this.Content = myFlowDocumentReader; } } }
The illustration below shows what this code snippet looks like.
In this example, the FlowDocumentReader control is used to host the flow content. See Flow Document Types for more information on flow content hosting controls. Paragraph, List, ListItem, and Bold elements are used to control content formatting, based on their order in markup. For example, the Bold element spans across only part of the text in the paragraph; as a result, only that part of the text is bold. If you have used HTML, this will be familiar to you.
As highlighted in the illustration above, there are several features built into Flow Documents :
Search: Allows the user to perform a full text search of an entire document.
Viewing Mode: The user can select their preferred viewing mode including a single-page (page-at-a-time) viewing mode, a two-page-at-a-time (book reading format) viewing mode, and a continuous scrolling (bottomless) viewing mode. For more information about these viewing modes, see FlowDocumentReaderViewingMode.
Page Navigation Controls: If the viewing mode of the document uses pages, the page navigation controls include a button to jump to the next page (the down arrow) or previous page (the up arrow), as well as indicators for the current page number and total number of pages. Flipping through pages can also be accomplished using the keyboard arrow keys.
Zoom: The zoom controls enable the user to increase or decrease the zoom level by clicking the plus or minus buttons, respectively. The zoom controls also include a slider for adjusting the zoom level. For more information, see Zoom.
These features can be modified based upon the control used to host the flow content. The next section describes the different controls.
Flow Document Types
Display of flow document content and how it appears is dependent upon what object is used to host the flow content. There are four controls that support viewing of flow content: FlowDocumentReader, FlowDocumentPageViewer, RichTextBox, and FlowDocumentScrollViewer. These controls are briefly described below.
Note: FlowDocument is required to directly host flow content, so all of these viewing controls consume a FlowDocument to enable flow content hosting .
FlowDocumentReader
FlowDocumentReader includes features that enable the user to dynamically choose between various viewing modes, including a single-page (page-at-a-time) viewing mode, a two-page-at-a-time (book reading format) viewing mode, and a continuous scrolling (bottomless) viewing mode. For more information about these viewing modes, see FlowDocumentReaderViewingMode. If you do not need the ability to dynamically switch between different viewing modes, FlowDocumentPageViewer and FlowDocumentScrollViewer provide lighter-weight flow content viewers that are fixed in a particular viewing mode.
FlowDocumentPageViewer and FlowDocumentScrollViewer
FlowDocumentPageViewer shows content in page-at-a-time viewing mode, while FlowDocumentScrollViewer shows content in continuous scrolling mode. Both FlowDocumentPageViewer and FlowDocumentScrollViewer are fixed to a particular viewing mode. Compare toFlowDocumentReader, which includes features that enable the user to dynamically choose between various viewing modes (as provided by theFlowDocumentReaderViewingMode enumeration), at the cost of being more resource intensive than FlowDocumentPageViewer orFlowDocumentScrollViewer.
By default, a vertical scrollbar is always shown, and a horizontal scrollbar becomes visible if needed. The default UI for FlowDocumentScrollViewerdoes not include a toolbar; however, the IsToolBarVisible property can be used to enable a built-in toolbar.
RichTextBox
You use a RichTextBox when you want to allow the user to edit flow content. For example, if you wanted to create an editor that allowed a user to manipulate things like tables, italic and bold formatting, etc, you would use a RichTextBox. See RichTextBox Overview for more information.
Note: Flow content inside a RichTextBox does not behave exactly like flow content contained in other controls. For example, there are no columns in a RichTextBox and hence no automatic resizing behavior. Also, the typically built in features of flow content like search, viewing mode, page navigation, and zoom are not available within a RichTextBox.
Creating Flow Content
Flow content can be complex, consisting of various elements including text, images, tables, and even UIElement derived classes like controls. To understand how to create complex flow content, the following points are critical:
Flow-related Classes: Each class used in flow content has a specific purpose. In addition, the hierarchical relation between flow classes helps you understand how they are used. For example, classes derived from the Block class are used to contain other objects while classes derived from Inline contain objects that are displayed.
Content Schema: A flow document can require a substantial number of nested elements. The content schema specifies possible parent/child relationships between elements.
The following sections will go over each of these areas in more detail.
Flow Related Classes
The diagram below shows the objects most typically used with flow content:
For the purposes of flow content, there are two important categories:
Block-derived classes: Also called "Block content elements" or just "Block Elements". Elements that inherit from Block can be used to group elements under a common parent or to apply common attributes to a group.
Inline-derived classes: Also called "Inline content elements" or just "Inline Elements". Elements that inherit from Inline are either contained within a Block Element or another Inline Element. Inline Elements are often used as the direct container of content that is rendered to the screen. For example, a Paragraph (Block Element) can contain a Run (Inline Element) but the Run actually contains the text that is rendered on the screen.
Each class in these two categories is briefly described below.
Block-derived Classes
Paragraph
Paragraph is typically used to group content into a paragraph. The simplest and most common use of Paragraph is to create a paragraph of text.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Paragraph> Some paragraph text. </Paragraph> </FlowDocument>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class ParagraphExample : Page { public ParagraphExample() { // Create paragraph with some text. Paragraph myParagraph = new Paragraph(); myParagraph.Inlines.Add(new Run("Some paragraph text.")); // Create a FlowDocument and add the paragraph to it. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(myParagraph); this.Content = myFlowDocument; } } }
However, you can also contain other inline-derived elements as you will see below .
Section
Section is used only to contain other Block-derived elements. It does not apply any default formatting to the elements it contains. However, any property values set on a Section applies to its child elements. A section also enables you to programmatically iterate through its child collection.Section is used in a similar manner to the <DIV> tag in HTML.
In the example below, three paragraphs are defined under one Section. The section has a Background property value of Red, therefore the background color of the paragraphs is also red.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- By default, Section applies no formatting to elements contained within it. However, in this example, the section has a Background property value of "Red", therefore, the three paragraphs (the block) inside the section also have a red background. --> <Section Background="Red"> <Paragraph> Paragraph 1 </Paragraph> <Paragraph> Paragraph 2 </Paragraph> <Paragraph> Paragraph 3 </Paragraph> </Section> </FlowDocument>
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class SectionExample : Page { public SectionExample() { // Create three paragraphs Paragraph myParagraph1 = new Paragraph(new Run("Paragraph 1")); Paragraph myParagraph2 = new Paragraph(new Run("Paragraph 2")); Paragraph myParagraph3 = new Paragraph(new Run("Paragraph 3")); // Create a Section and add the three paragraphs to it. Section mySection = new Section(); mySection.Background = Brushes.Red; mySection.Blocks.Add(myParagraph1); mySection.Blocks.Add(myParagraph2); mySection.Blocks.Add(myParagraph3); // Create a FlowDocument and add the section to it. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(mySection); this.Content = myFlowDocument; } } }
BlockUIContainer
BlockUIContainer enables UIElement elements (i.e. a Button) to be embedded in block-derived flow content. InlineUIContainer (see below) is used to embed UIElement elements in inline-derived flow content. BlockUIContainer and InlineUIContainer are important because there is no other way to use a UIElement in flow content unless it is contained within one of these two elements.
The following example shows how to use the BlockUIContainer element to host UIElement objects within flow content.
<FlowDocument ColumnWidth="400"> <Section Background="GhostWhite"> <Paragraph> A UIElement element may be embedded directly in flow content by enclosing it in a BlockUIContainer element. </Paragraph> <BlockUIContainer> <Button>Click me!</Button> </BlockUIContainer> <Paragraph> The BlockUIContainer element may host no more than one top-level UIElement. However, other UIElements may be nested within the UIElement contained by an BlockUIContainer element. For example, a StackPanel can be used to host multiple UIElement elements within a BlockUIContainer element. </Paragraph> <BlockUIContainer> <StackPanel> <Label Foreground="Blue">Choose a value:</Label> <ComboBox> <ComboBoxItem IsSelected="True">a</ComboBoxItem> <ComboBoxItem>b</ComboBoxItem> <ComboBoxItem>c</ComboBoxItem> </ComboBox> <Label Foreground ="Red">Choose a value:</Label> <StackPanel> <RadioButton>x</RadioButton> <RadioButton>y</RadioButton> <RadioButton>z</RadioButton> </StackPanel> <Label>Enter a value:</Label> <TextBox> A text editor embedded in flow content. </TextBox> </StackPanel> </BlockUIContainer> </Section> </FlowDocument>
The following figure shows how this example renders.
List
List is used to create a bulleted or numeric list. Set the MarkerStyle property to a TextMarkerStyle enumeration value to determine the style of the list. The example below shows how to create a simple list.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <List> <ListItem> <Paragraph> List Item 1 </Paragraph> </ListItem> <ListItem> <Paragraph> List Item 2 </Paragraph> </ListItem> <ListItem> <Paragraph> List Item 3 </Paragraph> </ListItem> </List> </FlowDocument>
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class ListExample : Page { public ListExample() { // Create three paragraphs Paragraph myParagraph1 = new Paragraph(new Run("List Item 1")); Paragraph myParagraph2 = new Paragraph(new Run("List Item 2")); Paragraph myParagraph3 = new Paragraph(new Run("List Item 3")); // Create the ListItem elements for the List and add the // paragraphs to them. ListItem myListItem1 = new ListItem(); myListItem1.Blocks.Add(myParagraph1); ListItem myListItem2 = new ListItem(); myListItem2.Blocks.Add(myParagraph2); ListItem myListItem3 = new ListItem(); myListItem3.Blocks.Add(myParagraph3); // Create a List and add the three ListItems to it. List myList = new List(); myList.ListItems.Add(myListItem1); myList.ListItems.Add(myListItem2); myList.ListItems.Add(myListItem3); // Create a FlowDocument and add the section to it. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(myList); this.Content = myFlowDocument; } } }
Note:List is the only flow element that uses the ListItemCollection to manage child elements.
Table
Table is used to create a table. Table is similar to the Grid element but it has more capabilities and, therefore, requires greater resource overhead. Because Grid is a UIElement, it cannot be used in flow content unless it is contained in a BlockUIContainer or InlineUIContainer. For more information on Table, see Table Overview.
Inline-derived Classes
Run
Run is used to contain unformatted text. You might expect Run objects to be used extensively in flow content. However, in markup, Run elements are not required to be used explicitly. Run is required to be used when creating or manipulating flow documents by using code. For example, in the markup below, the first Paragraph specifies the Run element explicitly while the second does not. Both paragraphs generate identical output.
<Paragraph> <Run>Paragraph that explicitly uses the Run element.</Run> </Paragraph> <Paragraph> This Paragraph omits the the Run element in markup. It renders the same as a Paragraph with Run used explicitly. </Paragraph>
Note: Starting in the .NET Framework 4, the Text property of the Run object is a dependency property. You can bind the Text property to a data source, such as a TextBlock. The Text property fully supports one-way binding. The Text property also supports two-way binding, except forRichTextBox. For an example, see Run.Text.
Span
Span groups other inline content elements together. No inherent rendering is applied to content within a Span element. However, elements that inherit from Span including Hyperlink, Bold, Italic and Underline do apply formatting to text.
Below is an example of a Span being used to contain inline content including text, a Bold element, and a Button.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Paragraph> Text before the Span. <Span Background="Red">Text within the Span is red and <Bold>this text is inside the Span-derived element Bold.</Bold> A Span can contain more then text, it can contain any inline content. For example, it can contain a <InlineUIContainer> <Button>Button</Button> </InlineUIContainer> or other UIElement, a Floater, a Figure, etc.</Span> </Paragraph> </FlowDocument>
The following screenshot shows how this example renders.
InlineUIContainer
InlineUIContainer enables UIElement elements (i.e. a control like Button) to be embedded in an Inline content element. This element is the inline equivalent to BlockUIContainer described above. Below is an example that uses InlineUIContainer to insert a Button inline in a Paragraph.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Paragraph> Text to precede the button... <!-- Set the BaselineAlignment property to "Bottom" so that the Button aligns properly with the text. --> <InlineUIContainer BaselineAlignment="Bottom"> <Button>Button</Button> </InlineUIContainer> Text to follow the button... </Paragraph> </FlowDocument>
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class InlineUIContainerExample : Page { public InlineUIContainerExample() { Run run1 = new Run(" Text to precede the button... "); Run run2 = new Run(" Text to follow the button... "); // Create a new button to be hosted in the paragraph. Button myButton = new Button(); myButton.Content = "Click me!"; // Create a new InlineUIContainer to contain the Button. InlineUIContainer myInlineUIContainer = new InlineUIContainer(); // Set the BaselineAlignment property to "Bottom" so that the // Button aligns properly with the text. myInlineUIContainer.BaselineAlignment = BaselineAlignment.Bottom; // Asign the button as the UI container's child. myInlineUIContainer.Child = myButton; // Create the paragraph and add content to it. Paragraph myParagraph = new Paragraph(); myParagraph.Inlines.Add(run1); myParagraph.Inlines.Add(myInlineUIContainer); myParagraph.Inlines.Add(run2); // Create a FlowDocument and add the paragraph to it. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(myParagraph); this.Content = myFlowDocument; } } }
Note:InlineUIContainer does not need to be used explicitly in markup. If you omit it, an InlineUIContainer will be created anyway when the code is compiled.
Figure and Floater
Figure and Floater are used to embed content in Flow Documents with placement properties that can be customized independent of the primary content flow. Figure or Floater elements are often used to highlight or accentuate portions of content, to host supporting images or other content within the main content flow, or to inject loosely related content such as advertisements.
The following example shows how to embed a Figure into a paragraph of text.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Paragraph> <Figure Width="300" Height="100" Background="GhostWhite" HorizontalAnchor="PageLeft" > <Paragraph FontStyle="Italic" Background="Beige" Foreground="DarkGreen" > A Figure embeds content into flow content with placement properties that can be customized independently from the primary content flow </Paragraph> </Figure> Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> </FlowDocument>
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class FigureExample : Page { public FigureExample() { // Create strings to use as content. string strFigure = "A Figure embeds content into flow content with" + " placement properties that can be customized" + " independently from the primary content flow"; string strOther = "Lorem ipsum dolor sit amet, consectetuer adipiscing" + " elit, sed diam nonummy nibh euismod tincidunt ut laoreet" + " dolore magna aliquam erat volutpat. Ut wisi enim ad" + " minim veniam, quis nostrud exerci tation ullamcorper" + " suscipit lobortis nisl ut aliquip ex ea commodo consequat." + " Duis autem vel eum iriure."; // Create a Figure and assign content and layout properties to it. Figure myFigure = new Figure(); myFigure.Width = new FigureLength(300); myFigure.Height = new FigureLength(100); myFigure.Background = Brushes.GhostWhite; myFigure.HorizontalAnchor = FigureHorizontalAnchor.PageLeft; Paragraph myFigureParagraph = new Paragraph(new Run(strFigure)); myFigureParagraph.FontStyle = FontStyles.Italic; myFigureParagraph.Background = Brushes.Beige; myFigureParagraph.Foreground = Brushes.DarkGreen; myFigure.Blocks.Add(myFigureParagraph); // Create the paragraph and add content to it. Paragraph myParagraph = new Paragraph(); myParagraph.Inlines.Add(myFigure); myParagraph.Inlines.Add(new Run(strOther)); // Create a FlowDocument and add the paragraph to it. FlowDocument myFlowDocument = new FlowDocument(); myFlowDocument.Blocks.Add(myParagraph); this.Content = myFlowDocument; } } }
The following illustration shows how this example renders.
Figure and Floater differ in several ways and are used for different scenarios.
Figure:
Can be positioned: You can set its horizontal and vertical anchors to dock it relative to the page, content, column or paragraph. You can also use its HorizontalOffset and VerticalOffset properties to specify arbitrary offsets.
Is sizable to more than one column: You can set Figure height and width to multiples of page, content or column height or width. Note that in the case of page and content, multiples greater than 1 are not allowed. For example, you can set the width of a Figure to be "0.5 page" or "0.25 content" or "2 Column". You can also set height and width to absolute pixel values.
Does not paginate: If the content inside a Figure does not fit inside the Figure, it will render whatever content does fit and the remaining content is lost
Floater:
Cannot be positioned and will render wherever space can be made available for it. You cannot set the offset or anchor a Floater.
Cannot be sized to more than one column: By default, Floater sizes at one column. It has a Width property that can be set to an absolute pixel value, but if this value is greater than one column width it is ignored and the floater is sized at one column. You can size it to less than one column by setting the correct pixel width, but sizing is not column-relative, so "0.5Column" is not a valid expression for Floater width.Floater has no height property and it's height cannot be set, it’s height depends on the content
Floater paginates: If its content at its specified width extends to more than 1 column height, floater breaks and paginates to the next column, the next page, etc.
Figure is a good place to put standalone content where you want to control the size and positioning, and are confident that the content will fit in the specified size. Floater is a good place to put more free-flowing content that flows similar to the main page content, but is separated from it.
LineBreak
LineBreak causes a line break to occur in flow content. The following example demonstrates the use of LineBreak.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Paragraph> Before the LineBreak in Paragraph. <LineBreak /> After the LineBreak in Paragraph. <LineBreak/><LineBreak/> After two LineBreaks in Paragraph. </Paragraph> <Paragraph> <LineBreak/> </Paragraph> <Paragraph> After a Paragraph with only a LineBreak in it. </Paragraph> </FlowDocument>
The following screenshot shows how this example renders.
Flow Collection Elements
In many of the examples above, the BlockCollection and InlineCollection are used to construct flow content programmatically. For example, to add elements to a Paragraph, you can use the syntax:
…
myParagraph.Inlines.Add(new Run("Some text"));
…
This adds a Run to the InlineCollection of the Paragraph. This is the same as the implicit Run found inside a Paragraph in markup:
…
<Paragraph>
Some Text
</Paragraph>
…
As an example of using the BlockCollection, the following example creates a new Section and then uses the Add method to add a new Paragraphto the Section contents.
Section secx = new Section(); secx.Blocks.Add(new Paragraph(new Run("A bit of text content...")));
In addition to adding items to a flow collection, you can remove items as well. The following example deletes the last Inline element in the Span.
The following example clears all of the contents ( Inline elements) from the Span.
When working with flow content programmatically, you will likely make extensive use of these collections.
Whether a flow element uses an InlineCollection (Inlines) or BlockCollection (Blocks) to contain its child elements depends on what type of child elements ( Block or Inline) can be contained by the parent. Containment rules for flow content elements are summarized in the content schema in the next section.
Note: There is a third type of collection used with flow content, the ListItemCollection, but this collection is only used with a List. In addition, there are several collections used with Table. See Table Overview for more information.
Content Schema
Given the number of different flow content elements, it can be overwhelming to keep track of what type of child elements an element can contain. The diagram below summarizes the containment rules for flow elements. The arrows represent the possible parent/child relationships.
As can be seen from the diagram above, the children allowed for an element are not necessarily determined by whether it is a Block element or anInline element. For example, a Span (an Inline element) can only have Inline child elements while a Figure (also an Inline element) can only have Blockchild elements. Therefore, a diagram is useful for quickly determining what element can be contained in another. As an example, let's use the diagram to determine how to construct the flow content of a RichTextBox.
1. A RichTextBox must contain a FlowDocument which in turn must contain a Block-derived object. Below is the corresponding segment from the diagram above.
Thus far, this is what the markup might look like.
<RichTextBox> <FlowDocument> <!-- One or more Block-derived object… --> </FlowDocument> </RichTextBox>
2. According to the diagram, there are several Block elements to choose from including Paragraph, Section, Table, List, and BlockUIContainer (see Block-derived classes above). Let's say we want a Table. According to the diagram above, a Table contains a TableRowGroup containing TableRowelements, which contain TableCell elements which contain a Block-derived object. Below is the corresponding segment for Table taken from the diagram above.
Below is the corresponding markup.
<RichTextBox> <FlowDocument> <Table> <TableRowGroup> <TableRow> <TableCell> <!-- One or more Block-derived object… --> </TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument> </RichTextBox>
3. Again, one or more Block elements are required underneath a TableCell. To make it simple, let's place some text inside the cell. We can do this using a Paragraph with a Run element. Below is the corresponding segments from the diagram showing that a Paragraph can take an Inline element and that a Run (an Inline element) can only take plain text.
Below is the entire example in markup.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <RichTextBox> <FlowDocument> <!-- Normally a table would have multiple rows and multiple cells but this code is for demonstration purposes.--> <Table> <TableRowGroup> <TableRow> <TableCell> <Paragraph> <!-- The schema does not actually require explicit use of the Run tag in markup. It is only included here for clarity. --> <Run>Paragraph in a Table Cell.</Run> </Paragraph> </TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument> </RichTextBox> </Page>
Customizing Text
Usually text is the most prevalent type of content in a flow document. Although the objects introduced above can be used to control most aspects of how text is rendered, there are some other methods for customizing text that is covered in this section.
Text Decorations
Text decorations allow you to apply the underline, overline, baseline, and strikethrough effects to text (see pictures below). These decorations are added using the TextDecorations property that is exposed by a number of objects including Inline, Paragraph, TextBlock, and TextBox.
The following example shows how to set the TextDecorations property of a Paragraph.
<FlowDocument ColumnWidth="200"> <Paragraph TextDecorations="Strikethrough"> This text will render with the strikethrough effect. </Paragraph> </FlowDocument>
Paragraph parx = new Paragraph(new Run("This text will render with the strikethrough effect.")); parx.TextDecorations = TextDecorations.Strikethrough;
The following figure shows how this example renders.
The following figures show how the Overline, Baseline, and Underline decorations render, respectively.
Typography
The Typography property is exposed by most flow-related content including TextElement, FlowDocument, TextBlock, and TextBox. This property is used to control typographical characteristics/variations of text (i.e. small or large caps, making superscripts and subscripts, etc).
The following example shows how to set the Typography attribute, using Paragraph as the example element.
<Paragraph TextAlignment="Left" FontSize="18" FontFamily="Palatino Linotype" Typography.NumeralStyle="OldStyle" Typography.Fraction="Stacked" Typography.Variants="Inferior" > <Run> This text has some altered typography characteristics. Note that use of an open type font is necessary for most typographic properties to be effective. </Run> <LineBreak/><LineBreak/> <Run> 0123456789 10 11 12 13 </Run> <LineBreak/><LineBreak/> <Run> 1/2 2/3 3/4 </Run> </Paragraph>
The following figure shows how this example renders.
In contrast, the following figure shows how a similar example with default typographic properties renders.
The following example shows how to set the Typography property programmatically.
Paragraph par = new Paragraph(); Run runText = new Run( "This text has some altered typography characteristics. Note" + "that use of an open type font is necessary for most typographic" + "properties to be effective."); Run runNumerals = new Run("0123456789 10 11 12 13"); Run runFractions = new Run("1/2 2/3 3/4"); par.Inlines.Add(runText); par.Inlines.Add(new LineBreak()); par.Inlines.Add(new LineBreak()); par.Inlines.Add(runNumerals); par.Inlines.Add(new LineBreak()); par.Inlines.Add(new LineBreak()); par.Inlines.Add(runFractions); par.TextAlignment = TextAlignment.Left; par.FontSize = 18; par.FontFamily = new FontFamily("Palatino Linotype"); par.Typography.NumeralStyle = FontNumeralStyle.OldStyle; par.Typography.Fraction = FontFraction.Stacked; par.Typography.Variants = FontVariants.Inferior;
See Typography in WPF for more information on typography.
TextElement Content Model Overview
This content model overview describes the supported content for a TextElement. The Paragraph class is a type of TextElement. A content model describes what objects/elements can be contained in others. This overview summarizes the content model used for objects derived from TextElement. For more information, see Flow Document Overview.
Content Model Diagram
The following diagram summarizes the content model for classes derived from TextElement as well as how other non- TextElement classes fit into this model.
As can be seen from the preceding diagram, the children allowed for an element are not necessarily determined by whether a class is derived from the Block class or an Inline class. For example, a Span (an Inline-derived class) can only have Inline child elements, but a Figure (also an Inline-derived class) can only have Block child elements. Therefore, a diagram is useful for quickly determining what element can be contained in another. As an example, let's use the diagram to determine how to construct the flow content of a RichTextBox.
A RichTextBox must contain a FlowDocument which in turn must contain a Block-derived object. The following is the corresponding segment from the preceding diagram.
Thus far, this is what the markup might look like.
<RichTextBox> <FlowDocument> <!-- One or more Block-derived object… --> </FlowDocument> </RichTextBox>
According to the diagram, there are several Block elements to choose from including Paragraph, Section, Table, List, and BlockUIContainer (see Block-derived classes in the preceding diagram). Let's say we want a Table. According to the preceding diagram, a Table contains aTableRowGroup containing TableRow elements, which contain TableCell elements which contain a Block-derived object. The following is the corresponding segment for Table taken from the preceding diagram.
The following is the corresponding markup.
<RichTextBox> <FlowDocument> <Table> <TableRowGroup> <TableRow> <TableCell> <!-- One or more Block-derived object… --> </TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument> </RichTextBox>
Again, one or more Block elements are required underneath a TableCell. To make it simple, let's place some text inside the cell. We can do this using a Paragraph with a Run element. The following is the corresponding segments from the diagram showing that a Paragraph can take anInline element and that a Run (an Inline element) can only take plain text.
The following is the entire example in markup.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <RichTextBox> <FlowDocument> <!-- Normally a table would have multiple rows and multiple cells but this code is for demonstration purposes.--> <Table> <TableRowGroup> <TableRow> <TableCell> <Paragraph> <!-- The schema does not actually require explicit use of the Run tag in markup. It is only included here for clarity. --> <Run>Paragraph in a Table Cell.</Run> </Paragraph> </TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument> </RichTextBox> </Page>
Working with TextElement Content Programmatically
The contents of a TextElement is made up by collections and so programmatically manipulating the contents of TextElement objects is done by working with these collections. There are three different collections used by TextElement -derived classes:
InlineCollection: Represents a collection of Inline elements. InlineCollection defines the allowable child content of the Paragraph, Span, andTextBlock elements.
BlockCollection: Represents a collection of Block elements. BlockCollection defines the allowable child content of the FlowDocument, Section,ListItem, TableCell, Floater, and Figure elements.
ListItemCollection: A flow content element that represents a particular content item in an ordered or unordered List.
You can manipulate (add or remove items) from these collections using the respective properties of Inlines, Blocks, and ListItems. The following examples show how to manipulate the contents of a Span using the Inlines property.
Note |
---|
Table uses several collections to manipulate its contents, but they are not covered here. For more information, see Table Overview. |
The following example creates a new Span object, and then uses the Add method to add two text runs as content children of the Span.
Span spanx = new Span(); spanx.Inlines.Add(new Run("A bit of text content...")); spanx.Inlines.Add(new Run("A bit more text content..."));
The following example creates a new Run element and inserts it at the beginning of the Span.
Run runx = new Run("Text to insert..."); spanx.Inlines.InsertBefore(spanx.Inlines.FirstInline, runx);
The following example deletes the last Inline element in the Span.
The following example clears all of the contents ( Inline elements) from the Span.
Types That Share This Content Model
The following types inherit from the TextElement class and may be used to display the content described in this overview.
Bold, Figure, Floater, Hyperlink, InlineUIContainer, Italic, LineBreak, List, ListItem, Paragraph, Run, Section, Span, Table, Underline.
Note that this list only includes nonabstract types distributed with the Windows SDK. You may use other types that inherit from TextElement.
See WPF Content Model.
Table Overview
Table is a block level element that supports grid-based presentation of Flow document content. The flexibility of this element makes it very useful, but also makes it more complicated to understand and use correctly.
This topic contains the following sections.
Table Basics
How is Table Different then Grid?
Table and Grid share some common functionality, but each is best suited for different scenarios. A Table is designed for use within flow content (see Flow Document Overview for more information on flow content). Grids are best used inside of forms (basically anywhere outside of flow content). Within a FlowDocument, Table supports flow content behaviors like pagination, column reflow, and content selection while a Grid does not. A Grid on the other hand is best used outside of a FlowDocument for many reasons including Grid adds elements based on a row and column index, Table does not. The Grid element allows layering of child content, allowing more than one element to exist within a single "cell." Table does not support layering. Child elements of a Grid can be absolutely positioned relative to the area of their "cell" boundaries. Table does not support this feature. Finally, a Grid requires less resources then a Table so consider using a Grid to improve performance.
Basic Table Structure
Table provides a grid-based presentation consisting of columns (represented by TableColumn elements) and rows (represented by TableRowelements). TableColumn elements do not host content; they simply define columns and characteristics of columns. TableRow elements must be hosted in a TableRowGroup element, which defines a grouping of rows for the table. TableCell elements, which contain the actual content to be presented by the table, must be hosted in a TableRow element. TableCell may only contain elements that derive from Block. Valid child elements for a TableCell include.
Note |
---|
TableCell elements may not directly host text content. For more information about the containment rules for flow content elements likeTableCell, see Flow Document Overview. |
The following example defines a simple 2 x 3 table with XAML.
<!-- Table is a Block element, and as such must be hosted in a container for Block elements. FlowDocument provides such a container. --> <FlowDocument> <Table> <!-- This table has 3 columns, each described by a TableColumn element nested in a Table.Columns collection element. --> <Table.Columns> <TableColumn /> <TableColumn /> <TableColumn /> </Table.Columns> <!-- This table includes a single TableRowGroup which hosts 2 rows, each described by a TableRow element. --> <TableRowGroup> <!-- Each of the 2 TableRow elements hosts 3 cells, described by TableCell elements. --> <TableRow> <TableCell> <!-- TableCell elements may only host elements derived from Block. In this example, Paragaph elements serve as the ultimate content containers for the cells in this table. --> <Paragraph>Cell at Row 1 Column 1</Paragraph> </TableCell> <TableCell> <Paragraph>Cell at Row 1 Column 2</Paragraph> </TableCell> <TableCell> <Paragraph>Cell at Row 1 Column 3</Paragraph> </TableCell> </TableRow> <TableRow> <TableCell> <Paragraph>Cell at Row 2 Column 1</Paragraph> </TableCell> <TableCell> <Paragraph>Cell at Row 2 Column 2</Paragraph> </TableCell> <TableCell> <Paragraph>Cell at Row 2 Column 3</Paragraph> </TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument>
The following figure shows how this example renders.
Table Containment
Row Groupings
The TableRowGroup element provides a way to arbitrarily group rows within a table; every row in a table must belong to a row grouping. Rows within a row group often share a common intent, and may be styled as a group. A common use for row groupings is to separate special-purpose rows, such as a title, header, and footer rows, from the primary content contained by the table.
The following example uses XAML to define a table with styled header and footer rows.
<Table> <Table.Resources> <!-- Style for header/footer rows. --> <Style x:Key="headerFooterRowStyle" TargetType="{x:Type TableRowGroup}"> <Setter Property="FontWeight" Value="DemiBold"/> <Setter Property="FontSize" Value="16"/> <Setter Property="Background" Value="LightGray"/> </Style> <!-- Style for data rows. --> <Style x:Key="dataRowStyle" TargetType="{x:Type TableRowGroup}"> <Setter Property="FontSize" Value="12"/> <Setter Property="FontStyle" Value="Italic"/> </Style> </Table.Resources> <Table.Columns> <TableColumn/> <TableColumn/> <TableColumn/> <TableColumn/> </Table.Columns> <!-- This TableRowGroup hosts a header row for the table. --> <TableRowGroup Style="{StaticResource headerFooterRowStyle}"> <TableRow> <TableCell/> <TableCell><Paragraph>Gizmos</Paragraph></TableCell> <TableCell><Paragraph>Thingamajigs</Paragraph></TableCell> <TableCell><Paragraph>Doohickies</Paragraph></TableCell> </TableRow> </TableRowGroup> <!-- This TableRowGroup hosts the main data rows for the table. --> <TableRowGroup Style="{StaticResource dataRowStyle}"> <TableRow> <TableCell><Paragraph Foreground="Blue">Blue</Paragraph></TableCell> <TableCell><Paragraph>1</Paragraph></TableCell> <TableCell><Paragraph>2</Paragraph></TableCell> <TableCell><Paragraph>3</Paragraph> </TableCell> </TableRow> <TableRow> <TableCell><Paragraph Foreground="Red">Red</Paragraph></TableCell> <TableCell><Paragraph>1</Paragraph></TableCell> <TableCell><Paragraph>2</Paragraph></TableCell> <TableCell><Paragraph>3</Paragraph></TableCell> </TableRow> <TableRow> <TableCell><Paragraph Foreground="Green">Green</Paragraph></TableCell> <TableCell><Paragraph>1</Paragraph></TableCell> <TableCell><Paragraph>2</Paragraph></TableCell> <TableCell><Paragraph>3</Paragraph></TableCell> </TableRow> </TableRowGroup> <!-- This TableRowGroup hosts a footer row for the table. --> <TableRowGroup Style="{StaticResource headerFooterRowStyle}"> <TableRow> <TableCell><Paragraph>Totals</Paragraph></TableCell> <TableCell><Paragraph>3</Paragraph></TableCell> <TableCell><Paragraph>6</Paragraph></TableCell> <TableCell> <Table></Table> </TableCell> </TableRow> </TableRowGroup> </Table>
The following figure shows how this example renders.
Background Rendering Precedence
Table elements render in the following order (z-order from lowest to highest). This order cannot be changed. For example, there is no "Z-order" property for these elements that you can use to override this established order.
Consider the following example, which defines background colors for each of these elements within a table.
<Table Background="Yellow"> <Table.Columns> <TableColumn/> <TableColumn Background="LightGreen"/> <TableColumn/> </Table.Columns> <TableRowGroup> <TableRow> <TableCell/><TableCell/><TableCell/> </TableRow> </TableRowGroup> <TableRowGroup Background="Tan"> <TableRow> <TableCell/><TableCell/><TableCell/> </TableRow> <TableRow Background="LightBlue"> <TableCell/><TableCell Background="Purple"/><TableCell/> </TableRow> <TableRow> <TableCell/><TableCell/><TableCell/> </TableRow> </TableRowGroup> <TableRowGroup> <TableRow> <TableCell/><TableCell/><TableCell/> </TableRow> </TableRowGroup> </Table>
The following figure shows how this example renders (showing background colors only).
Spanning Rows or Columns
Table cells may be configured to span multiple rows or columns by using the RowSpan or ColumnSpan attributes, respectively.
Consider the following example, in which a cell spans three columns.
<Table> <Table.Columns> <TableColumn/> <TableColumn/> <TableColumn/> </Table.Columns> <TableRowGroup> <TableRow> <TableCell ColumnSpan="3" Background="Cyan"> <Paragraph>This cell spans all three columns.</Paragraph> </TableCell> </TableRow> <TableRow> <TableCell Background="LightGray"><Paragraph>Cell 1</Paragraph></TableCell> <TableCell Background="LightGray"><Paragraph>Cell 2</Paragraph></TableCell> <TableCell Background="LightGray"><Paragraph>Cell 3</Paragraph></TableCell> </TableRow> </TableRowGroup> </Table>
The following figure shows how this example renders.
Building a Table With Code
The following examples show how to programmatically create a Table and populate it with content. The contents of the table are apportioned into five rows (represented by TableRow objects contained in a RowGroups object) and six columns (represented by TableColumn objects). The rows are used for different presentation purposes, including a title row intended to title the entire table, a header row to describe the columns of data in the table, and a footer row with summary information. Note that the notion of "title", "header", and "footer" rows are not inherent to the table; these are simply rows with different characteristics. Table cells contain the actual content, which can be comprised of text, images, or nearly any other user interface (UI) element.
First, a FlowDocument is created to host the Table, and a new Table is created and added to the contents of the FlowDocument.
// Create the parent FlowDocument... flowDoc = new FlowDocument(); // Create the Table... table1 = new Table(); // ...and add it to the FlowDocument Blocks collection. flowDoc.Blocks.Add(table1); // Set some global formatting properties for the table. table1.CellSpacing = 10; table1.Background = Brushes.White;
Next, six TableColumn objects are created and added to the table's Columns collection, with some formatting applied.
Note |
---|
Note that the table's Columns collection uses standard zero-based indexing. |
// Create 6 columns and add them to the table's Columns collection. int numberOfColumns = 6; for (int x = 0; x < numberOfColumns; x++) { table1.Columns.Add(new TableColumn()); // Set alternating background colors for the middle colums. if(x%2 == 0) table1.Columns[x].Background = Brushes.Beige; else table1.Columns[x].Background = Brushes.LightSteelBlue; }
Next, a title row is created and added to the table with some formatting applied. The title row happens to contain a single cell that spans all six columns in the table.
// Create and add an empty TableRowGroup to hold the table's Rows. table1.RowGroups.Add(new TableRowGroup()); // Add the first (title) row. table1.RowGroups[0].Rows.Add(new TableRow()); // Alias the current working row for easy reference. TableRow currentRow = table1.RowGroups[0].Rows[0]; // Global formatting for the title row. currentRow.Background = Brushes.Silver; currentRow.FontSize = 40; currentRow.FontWeight = System.Windows.FontWeights.Bold; // Add the header row with content, currentRow.Cells.Add(new TableCell(new Paragraph(new Run("2004 Sales Project")))); // and set the row to span all 6 columns. currentRow.Cells[0].ColumnSpan = 6;
Next, a header row is created and added to the table, and the cells in the header row are created and populated with content.
// Add the second (header) row. table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[1]; // Global formatting for the header row. currentRow.FontSize = 18; currentRow.FontWeight = FontWeights.Bold; // Add cells with content to the second row. currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Product")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 1")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 2")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 3")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 4")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("TOTAL"))));
Next, a row for data is created and added to the table, and the cells in this row are created and populated with content. Building this row is similar to building the header row, with slightly different formatting applied.
// Add the third row. table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[2]; // Global formatting for the row. currentRow.FontSize = 12; currentRow.FontWeight = FontWeights.Normal; // Add cells with content to the third row. currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Widgets")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$50,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$55,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$60,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$65,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$230,000")))); // Bold the first cell. currentRow.Cells[0].FontWeight = FontWeights.Bold;
Finally, a footer row is created, added, and formatted. Like the title row, the footer contains a single cell that spans all six columns in the table.
table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[3]; // Global formatting for the footer row. currentRow.Background = Brushes.LightGray; currentRow.FontSize = 18; currentRow.FontWeight = System.Windows.FontWeights.Normal; // Add the header row with content, currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Projected 2004 Revenue: $810,000")))); // and set the row to span all 6 columns. currentRow.Cells[0].ColumnSpan = 6;
How-to Topics
How to: Adjust Spacing Between Paragraphs
This example shows how to adjust or eliminate spacing between paragraphs in flow content.
In flow content, extra space that appears between paragraphs is the result of margins set on these paragraphs; thus, the spacing between paragraphs can be controlled by adjusting the margins on those paragraphs. To eliminate extra spacing between two paragraphs altogether, set the margins for the paragraphs to 0. To achieve uniform spacing between paragraphs throughout an entire FlowDocument, use styling to set a uniform margin value for all paragraphs in the FlowDocument.
It is important to note that the margins for two adjacent paragraphs will "collapse" to the larger of the two margins, rather than doubling up. So, if two adjacent paragraphs have margins of 20 pixels and 40 pixels respectively, the resulting space between the paragraphs is 40 pixels, the larger of the two margin values.
Example
The following example uses styling to set the margin for all Paragraph elements in a FlowDocument to 0, which effectively eliminates extra spacing between paragraphs in the FlowDocument.
<FlowDocument> <FlowDocument.Resources> <!-- This style is used to set the margins for all paragraphs in the FlowDocument to 0. --> <Style TargetType="{x:Type Paragraph}"> <Setter Property="Margin" Value="0"/> </Style> </FlowDocument.Resources> <Paragraph> Spacing between paragraphs is caused by margins set on the paragraphs. Two adjacent margins will "collapse" to the larger of the two margin widths, rather than doubling up. </Paragraph> <Paragraph> To eliminate extra spacing between two paragraphs, just set the paragraph margins to 0. </Paragraph> </FlowDocument>
How to: Build a Table Programmatically
The following examples show how to programmatically create a Table and populate it with content. The contents of the table are apportioned into five rows (represented by TableRow objects contained in a RowGroups object) and six columns (represented by TableColumn objects). The rows are used for different presentation purposes, including a title row intended to title the entire table, a header row to describe the columns of data in the table, and a footer row with summary information. Note that the notion of "title", "header", and "footer" rows are not inherent to the table; these are simply rows with different characteristics. Table cells contain the actual content, which can be comprised of text, images, or nearly any other user interface (UI) element.
Example
First, a FlowDocument is created to host the Table, and a new Table is created and added to the contents of the FlowDocument.
// Create the parent FlowDocument... flowDoc = new FlowDocument(); // Create the Table... table1 = new Table(); // ...and add it to the FlowDocument Blocks collection. flowDoc.Blocks.Add(table1); // Set some global formatting properties for the table. table1.CellSpacing = 10; table1.Background = Brushes.White;
Example
Next, six TableColumn objects are created and added to the table's Columns collection, with some formatting applied.
Note |
---|
Note that the table's Columns collection uses standard zero-based indexing. |
// Create 6 columns and add them to the table's Columns collection. int numberOfColumns = 6; for (int x = 0; x < numberOfColumns; x++) { table1.Columns.Add(new TableColumn()); // Set alternating background colors for the middle colums. if(x%2 == 0) table1.Columns[x].Background = Brushes.Beige; else table1.Columns[x].Background = Brushes.LightSteelBlue; }
Example
Next, a title row is created and added to the table with some formatting applied. The title row happens to contain a single cell that spans all six columns in the table.
// Create and add an empty TableRowGroup to hold the table's Rows. table1.RowGroups.Add(new TableRowGroup()); // Add the first (title) row. table1.RowGroups[0].Rows.Add(new TableRow()); // Alias the current working row for easy reference. TableRow currentRow = table1.RowGroups[0].Rows[0]; // Global formatting for the title row. currentRow.Background = Brushes.Silver; currentRow.FontSize = 40; currentRow.FontWeight = System.Windows.FontWeights.Bold; // Add the header row with content, currentRow.Cells.Add(new TableCell(new Paragraph(new Run("2004 Sales Project")))); // and set the row to span all 6 columns. currentRow.Cells[0].ColumnSpan = 6;
Example
Next, a header row is created and added to the table, and the cells in the header row are created and populated with content.
// Add the second (header) row. table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[1]; // Global formatting for the header row. currentRow.FontSize = 18; currentRow.FontWeight = FontWeights.Bold; // Add cells with content to the second row. currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Product")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 1")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 2")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 3")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Quarter 4")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("TOTAL"))));
Example
Next, a row for data is created and added to the table, and the cells in this row are created and populated with content. Building this row is similar to building the header row, with slightly different formatting applied.
// Add the third row. table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[2]; // Global formatting for the row. currentRow.FontSize = 12; currentRow.FontWeight = FontWeights.Normal; // Add cells with content to the third row. currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Widgets")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$50,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$55,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$60,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$65,000")))); currentRow.Cells.Add(new TableCell(new Paragraph(new Run("$230,000")))); // Bold the first cell. currentRow.Cells[0].FontWeight = FontWeights.Bold;
Example
Finally, a footer row is created, added, and formatted. Like the title row, the footer contains a single cell that spans all six columns in the table.
table1.RowGroups[0].Rows.Add(new TableRow()); currentRow = table1.RowGroups[0].Rows[3]; // Global formatting for the footer row. currentRow.Background = Brushes.LightGray; currentRow.FontSize = 18; currentRow.FontWeight = System.Windows.FontWeights.Normal; // Add the header row with content, currentRow.Cells.Add(new TableCell(new Paragraph(new Run("Projected 2004 Revenue: $810,000")))); // and set the row to span all 6 columns. currentRow.Cells[0].ColumnSpan = 6;
How to: Change the FlowDirection of Content Programmatically
This example shows how to programmatically change the FlowDirection property of a FlowDocumentReader.
Example
Two Button elements are created, each representing one of the possible values of FlowDirection. When a button is clicked, the associated property value is applied to the contents of a FlowDocumentReader named tf1. The property value is also written to a TextBlock named txt1.
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,10"> <Button Click="LR">LeftToRight</Button> <Button Click="RL">RightToLeft</Button> </StackPanel> <TextBlock Name="txt1" DockPanel.Dock="Bottom" Margin="0,50,0,0"/> <FlowDocumentReader> <FlowDocument FontFamily="Arial" Name="tf1"> <Paragraph> Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut uliquip ex ea commodo consequat. Duis autem vel eum iriure. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> <Paragraph> Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> </FlowDocument> </FlowDocumentReader>
Example
The events associated with the button clicks defined above are handled in a C# code-behind file.
private void LR(object sender, RoutedEventArgs e) { tf1.FlowDirection = FlowDirection.LeftToRight; txt1.Text = "FlowDirection is now " + tf1.FlowDirection; } private void RL(object sender, RoutedEventArgs e) { tf1.FlowDirection = FlowDirection.RightToLeft; txt1.Text = "FlowDirection is now " + tf1.FlowDirection; }
How to: Change the TextWrapping Property Programmatically
Example
The following code example shows how to change the value of the TextWrapping property programmatically.
Three Button elements are placed within a StackPanel element in XAML. Each Click event for a Button corresponds with an event handler in the code. The event handlers use the same name as the TextWrapping value they will apply to txt2 when the button is clicked. Also, the text in txt1 (aTextBlock not shown in the XAML) is updated to reflect the change in the property.
<StackPanel Orientation="Horizontal" Margin="0,0,0,20"> <Button Name="btn1" Background="Silver" Width="100" Click="Wrap">Wrap</Button> <Button Name="btn2" Background="Silver" Width="100" Click="NoWrap">NoWrap</Button> <Button Name="btn4" Background="Silver" Width="100" Click="WrapWithOverflow">WrapWithOverflow</Button> </StackPanel> <TextBlock Name="txt2" TextWrapping="Wrap" Margin="0,0,0,20" Foreground="Black"> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Lorem ipsum dolor sit aet, consectetuer adipiscing elit. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. </TextBlock>
private void Wrap(object sender, RoutedEventArgs e) { txt2.TextWrapping = System.Windows.TextWrapping.Wrap; txt1.Text = "The TextWrap property is currently set to Wrap."; } private void NoWrap(object sender, RoutedEventArgs e) { txt2.TextWrapping = System.Windows.TextWrapping.NoWrap; txt1.Text = "The TextWrap property is currently set to NoWrap."; } private void WrapWithOverflow(object sender, RoutedEventArgs e) { txt2.TextWrapping = System.Windows.TextWrapping.WrapWithOverflow; txt1.Text = "The TextWrap property is currently set to WrapWithOverflow."; }
How to: Define a Table with XAML
The following example demonstrates how to define a Table using Extensible Application Markup Language (XAML). The example table has four columns (represented by TableColumn elements) and several rows (represented by TableRow elements) containing data as well as title, header, and footer information. Rows must be contained in a TableRowGroup element. Each row in the table is comprised of one or more cells (represented by TableCellelements). Content in a table cell must be contained in a Block element; in this case Paragraph elements are used. The table also hosts a hyperlink (represented by the Hyperlink element) in the footer row.
Example
<FlowDocumentReader> <FlowDocument> <Table CellSpacing="5"> <Table.Columns> <TableColumn/> <TableColumn/> <TableColumn/> <TableColumn/> </Table.Columns> <TableRowGroup> <!-- Title row for the table. --> <TableRow Background="SkyBlue"> <TableCell ColumnSpan="4" TextAlignment="Center"> <Paragraph FontSize="24pt" FontWeight="Bold">Planetary Information</Paragraph> </TableCell> </TableRow> <!-- Header row for the table. --> <TableRow Background="LightGoldenrodYellow"> <TableCell><Paragraph FontSize="14pt" FontWeight="Bold">Planet</Paragraph></TableCell> <TableCell><Paragraph FontSize="14pt" FontWeight="Bold">Mean Distance from Sun</Paragraph></TableCell> <TableCell><Paragraph FontSize="14pt" FontWeight="Bold">Mean Diameter</Paragraph></TableCell> <TableCell><Paragraph FontSize="14pt" FontWeight="Bold">Approximate Mass</Paragraph></TableCell> </TableRow> <!-- Sub-title row for the inner planets. --> <TableRow> <TableCell ColumnSpan="4"><Paragraph FontSize="14pt" FontWeight="Bold">The Inner Planets</Paragraph></TableCell> </TableRow> <!-- Four data rows for the inner planets. --> <TableRow> <TableCell><Paragraph>Mercury</Paragraph></TableCell> <TableCell><Paragraph>57,910,000 km</Paragraph></TableCell> <TableCell><Paragraph>4,880 km</Paragraph></TableCell> <TableCell><Paragraph>3.30e23 kg</Paragraph></TableCell> </TableRow> <TableRow Background="lightgray"> <TableCell><Paragraph>Venus</Paragraph></TableCell> <TableCell><Paragraph>108,200,000 km</Paragraph></TableCell> <TableCell><Paragraph>12,103.6 km</Paragraph></TableCell> <TableCell><Paragraph>4.869e24 kg</Paragraph></TableCell> </TableRow> <TableRow> <TableCell><Paragraph>Earth</Paragraph></TableCell> <TableCell><Paragraph>149,600,000 km</Paragraph></TableCell> <TableCell><Paragraph>12,756.3 km</Paragraph></TableCell> <TableCell><Paragraph>5.972e24 kg</Paragraph></TableCell> </TableRow> <TableRow Background="lightgray"> <TableCell><Paragraph>Mars</Paragraph></TableCell> <TableCell><Paragraph>227,940,000 km</Paragraph></TableCell> <TableCell><Paragraph>6,794 km</Paragraph></TableCell> <TableCell><Paragraph>6.4219e23 kg</Paragraph></TableCell> </TableRow> <!-- Sub-title row for the outter planets. --> <TableRow> <TableCell ColumnSpan="4"><Paragraph FontSize="14pt" FontWeight="Bold">The Major Outer Planets</Paragraph></TableCell> </TableRow> <!-- Four data rows for the major outter planets. --> <TableRow> <TableCell><Paragraph>Jupiter</Paragraph></TableCell> <TableCell><Paragraph>778,330,000 km</Paragraph></TableCell> <TableCell><Paragraph>142,984 km</Paragraph></TableCell> <TableCell><Paragraph>1.900e27 kg</Paragraph></TableCell> </TableRow> <TableRow Background="lightgray"> <TableCell><Paragraph>Saturn</Paragraph></TableCell> <TableCell><Paragraph>1,429,400,000 km</Paragraph></TableCell> <TableCell><Paragraph>120,536 km</Paragraph></TableCell> <TableCell><Paragraph>5.68e26 kg</Paragraph></TableCell> </TableRow> <TableRow> <TableCell><Paragraph>Uranus</Paragraph></TableCell> <TableCell><Paragraph>2,870,990,000 km</Paragraph></TableCell> <TableCell><Paragraph>51,118 km</Paragraph></TableCell> <TableCell><Paragraph>8.683e25 kg</Paragraph></TableCell> </TableRow> <TableRow Background="lightgray"> <TableCell><Paragraph>Neptune</Paragraph></TableCell> <TableCell><Paragraph>4,504,000,000 km</Paragraph></TableCell> <TableCell><Paragraph>49,532 km</Paragraph></TableCell> <TableCell><Paragraph>1.0247e26 kg</Paragraph></TableCell> </TableRow> <!-- Footer row for the table. --> <TableRow> <TableCell ColumnSpan="4"><Paragraph FontSize="10pt" FontStyle="Italic"> Information from the <Hyperlink NavigateUri="http://encarta.msn.com/encnet/refpages/artcenter.aspx">Encarta</Hyperlink> web site. </Paragraph></TableCell> </TableRow> </TableRowGroup> </Table> </FlowDocument> </FlowDocumentReader>
How-to: Alter the Typography of Text
The following example shows how to set the Typography attribute, using Paragraph as the example element.
Example
<Paragraph TextAlignment="Left" FontSize="18" FontFamily="Palatino Linotype" Typography.NumeralStyle="OldStyle" Typography.Fraction="Stacked" Typography.Variants="Inferior" > <Run> This text has some altered typography characteristics. Note that use of an open type font is necessary for most typographic properties to be effective. </Run> <LineBreak/><LineBreak/> <Run> 0123456789 10 11 12 13 </Run> <LineBreak/><LineBreak/> <Run> 1/2 2/3 3/4 </Run> </Paragraph>
The following figure shows how this example renders.
In contrast, the following figure shows how a similar example with default typographic properties renders.
Example
The following example shows how to set the Typography property programmatically.
Paragraph par = new Paragraph(); Run runText = new Run( "This text has some altered typography characteristics. Note" + "that use of an open type font is necessary for most typographic" + "properties to be effective."); Run runNumerals = new Run("0123456789 10 11 12 13"); Run runFractions = new Run("1/2 2/3 3/4"); par.Inlines.Add(runText); par.Inlines.Add(new LineBreak()); par.Inlines.Add(new LineBreak()); par.Inlines.Add(runNumerals); par.Inlines.Add(new LineBreak()); par.Inlines.Add(new LineBreak()); par.Inlines.Add(runFractions); par.TextAlignment = TextAlignment.Left; par.FontSize = 18; par.FontFamily = new FontFamily("Palatino Linotype"); par.Typography.NumeralStyle = FontNumeralStyle.OldStyle; par.Typography.Fraction = FontFraction.Stacked; par.Typography.Variants = FontVariants.Inferior;
How to: Enable Text Trimming
This example demonstrates the usage and effects of the values available in the TextTrimming enumeration.
Example
The following example defines a TextBlock element with the TextTrimming attribute set.
<TextBlock Name="myTextBlock" Margin="20" Background="LightGoldenrodYellow" TextTrimming="WordEllipsis" TextWrapping="NoWrap" FontSize="14" > One<LineBreak/> two two<LineBreak/> Three Three Three<LineBreak/> four four four four<LineBreak/> Five Five Five Five Five<LineBreak/> six six six six six six<LineBreak/> Seven Seven Seven Seven Seven Seven Seven </TextBlock>
Example
Setting the corresponding TextTrimming property in code is demonstrated below.
There are currently three options for trimming text: CharacterEllipsis, WordEllipsis, and None.
When TextTrimming is set to CharacterEllipsis, text is trimmed and continued with an ellipsis at the character closest to the trimming edge. This setting tends to trim text to fit more closely to the trimming boundary, but may result in words being partially trimmed. The following figure shows the effect of this setting on a TextBlock similar to the one defined above.
When TextTrimming is set to WordEllipsis, text is trimmed and continued with an ellipsis at the end of the first full word closest to the trimming edge. This setting will not show partially trimmed words, but tends not to trim text as closely to the trimming edge as the CharacterEllipsis setting. The following figure shows the effect of this setting on the TextBlock defined above.
When TextTrimming is set to None, no text trimming is performed. In this case, text is simply cropped to the boundary of the parent text container. The following figure shows the effect of this setting on a TextBlock similar to the one defined above.
How to: Insert an Element Into Text Programmatically
The following example shows how to use two TextPointer objects to specify a range within text to apply a Span element to.
Example
using System; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Documents; namespace SDKSample { public partial class InsertInlineIntoTextExample : Page { public InsertInlineIntoTextExample() { // Create a paragraph with a short sentence Paragraph myParagraph = new Paragraph(new Run("Neptune has 72 times Earth's volume...")); // Create two TextPointers that will specify the text range the Span will cover TextPointer myTextPointer1 = myParagraph.ContentStart.GetPositionAtOffset(10); TextPointer myTextPointer2 = myParagraph.ContentEnd.GetPositionAtOffset(-5); // Create a Span that covers the range between the two TextPointers. Span mySpan = new Span(myTextPointer1, myTextPointer2); mySpan.Background = Brushes.Red; // Create a FlowDocument with the paragraph as its initial content. FlowDocument myFlowDocument = new FlowDocument(myParagraph); this.Content = myFlowDocument; } } }
The illustration below shows what this example looks like.
How to: Manipulate Flow Content Elements through the Blocks Property
These examples demonstrate some of the more common operations that can be performed on flow content elements through the Blocks property. This property is used to add and remove items from BlockCollection. Flow content elements that feature a Blocks property include:
These examples happen to use Section as the flow content element, but these techniques are applicable to all elements that host a flow content element collection.
Example
The following example creates a new Section and then uses the Add method to add a new Paragraph to the Section contents.
Example
Example
Example
How to: Manipulate Flow Content Elements through the Inlines Property
These examples demonstrate some of the more common operations that can be performed on inline flow content elements (and containers of such elements, such as TextBlock) through the Inlines property. This property is used to add and remove items from InlineCollection. Flow content elements that feature an Inlines property include:
These examples happen to use Span as the flow content element, but these techniques are applicable to all elements or controls that host an InlineCollection collection.
Example
Example
Example
Example
How to: Manipulate a FlowDocument through the Blocks Property
These examples demonstrate some of the more common operations that can be performed on a FlowDocument through the Blocks property.
Example
The following example creates a new FlowDocument and then appends a new Paragraph element to the FlowDocument.
Example
The following example creates a new Paragraph element and inserts it at the beginning of the FlowDocument.
Example
The following example gets the number of top-level Block elements contained in the FlowDocument.
Example
The following example deletes the last Block element in the FlowDocument.
Example
The following example clears all of the contents (Block elements) from the FlowDocument.
How to: Manipulate a Table's Columns through the Columns Property
This example demonstrates some of the more common operations that can be performed on a table's columns through the Columns property.
Example
Example
The following example inserts a new TableColumn. The new column is inserted at index position 0, making it the new first column in the table.
Note |
---|
The TableColumnCollection collection uses standard zero-based indexing. |
Example
The following example accesses some arbitrary properties on columns in the TableColumnCollection collection, referring to particular columns by index.
Example
The following example gets the number of columns currently hosted by the table.
Example
The following example removes a particular column by reference.
How to: Manipulate a Table's Row Groups through the RowGroups Property
This example demonstrates some of the more common operations that can be performed on a table's row groups through the RowGroups property.
Example
Example
The following example inserts a new TableRowGroup. The new column is inserted at index position 0, making it the new first row group in the table.
Note |
---|
The TableRowGroupCollection collection uses standard zero-based indexing. |
Example
The following example adds several rows to a particular TableRowGroup (specified by index) in the table.
Example
The following example accesses some arbitrary properties on rows in the first row group in the table.
Example
The following example adds several cells to a particular TableRow (specified by index) in the table.
Example
The following example access some arbitrary methods and properties on cells in the first row in the first row group.
Example
The following example returns the number of TableRowGroup elements hosted by the table.
Example
The following example removes a particular row group by reference.
How to: Use Flow Content Elements
The following example demonstrates declarative usage for various flow content elements and associated attributes. Elements and attributes demonstrated include:
Example
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Paragraph FontSize="18">Flow Format Example</Paragraph> <Paragraph> Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> <Paragraph> Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> <Paragraph FontSize="18">More flow elements</Paragraph> <Paragraph FontSize="15">Inline, font type and weight, and a List</Paragraph> <List> <ListItem><Paragraph>ListItem 1</Paragraph></ListItem> <ListItem><Paragraph>ListItem 2</Paragraph></ListItem> <ListItem><Paragraph>ListItem 3</Paragraph></ListItem> <ListItem><Paragraph>ListItem 4</Paragraph></ListItem> <ListItem><Paragraph>ListItem 5</Paragraph></ListItem> </List> <Paragraph><Bold>Bolded</Bold></Paragraph> <Paragraph><Underline>Underlined</Underline></Paragraph> <Paragraph><Bold><Underline>Bolded and Underlined</Underline></Bold></Paragraph> <Paragraph><Italic>Italic</Italic></Paragraph> <Paragraph><Span>The Span element, no inherent rendering</Span></Paragraph> <Paragraph><Run>The Run element, no inherent rendering</Run></Paragraph> <Paragraph FontSize="15">Subscript, Superscript</Paragraph> <Paragraph> <Run Typography.Variants="Superscript">This text is Superscripted.</Run> This text isn't. </Paragraph> <Paragraph> <Run Typography.Variants="Subscript">This text is Subscripted.</Run> This text isn't. </Paragraph> <Paragraph> If a font does not support a particular form (such as Superscript) a default font form will be displayed. </Paragraph> <Paragraph FontSize="15">Blocks, breaks, paragraph</Paragraph> <Section><Paragraph>A block section of text</Paragraph></Section> <Section><Paragraph>Another block section of text</Paragraph></Section> <Paragraph><LineBreak/></Paragraph> <Section><Paragraph>... and another section, preceded by a LineBreak</Paragraph></Section> <Section BreakPageBefore="True"/> <Section><Paragraph>... and another section, preceded by a PageBreak</Paragraph></Section> <Paragraph>Finally, a paragraph. Note the break between this paragraph ...</Paragraph> <Paragraph TextIndent="25">... and this paragraph, and also the left indention.</Paragraph> <Paragraph><LineBreak/></Paragraph> </FlowDocument>
How to: Use FlowDocument Column-Separating Attributes
This example shows how to use the column-separating features of a FlowDocument.
Example
The following example defines a FlowDocument, and sets the ColumnGap, ColumnRuleBrush, and ColumnRuleWidth attributes. The FlowDocumentcontains a single paragraph of sample content.
<FlowDocumentReader> <FlowDocument ColumnGap="20.0" ColumnRuleBrush="DodgerBlue" ColumnRuleWidth="5.0" ColumnWidth="140.0" > <Paragraph Background="AntiqueWhite" TextAlignment="Left"> This paragraph has the background set to antique white to make its boundaries obvious. The column gap is the space between columns; this FlowDocument will have a column gap of 20 device-independend pixels. The column rule is a vertical line drawn in the column gap, and is used to visually separate columns; this FlowDocument a Dodger-blue column rule that is 5 pixels wide. The column rule and column gap both take space between columns. In this case, a column gap width of 20 plus a column rule of width of 5 results in the space between columns being 25 pixels wide, 5 pixels for the column rule, and 10 pixels of column gap on either side of the column rule. </Paragraph> </FlowDocument> </FlowDocumentReader>
The following figure shows the effects of the ColumnGap, ColumnRuleBrush, and ColumnRuleWidth attributes in a rendered FlowDocument.
Typography
Typography in WPF
This topic introduces the major typographic features of WPF. These features include improved quality and performance of text rendering, OpenType typography support, enhanced international text, enhanced font support, and new text application programming interfaces (APIs).
Improved Quality and Performance of Text
Text in WPF is rendered using Microsoft ClearType, which enhances the clarity and readability of text. ClearType is a software technology developed by Microsoft that improves the readability of text on existing LCDs (Liquid Crystal Displays), such as laptop screens, Pocket PC screens and flat panel monitors. ClearType uses sub-pixel rendering which allows text to be displayed with a greater fidelity to its true shape by aligning characters on a fractional part of a pixel. The extra resolution increases the sharpness of the tiny details in text display, making it much easier to read over long durations. Another improvement of ClearType in WPF is y-direction anti-aliasing, which smoothes the tops and bottoms of shallow curves in text characters. For more details on ClearType features, see ClearType Overview.
The entire text rendering pipeline can be hardware-accelerated in WPF provided your machine meets the minimum level of hardware required. Rendering that cannot be performed using hardware falls back to software rendering. Hardware-acceleration affects all phases of the text rendering pipeline—from storing individual glyphs, compositing glyphs into glyph runs, applying effects, to applying the ClearType blending algorithm to the final displayed output. For more information on hardware acceleration, see Graphics Rendering Tiers.
In addition, animated text, whether by character or glyph, takes full advantage of the graphics hardware capability enabled by WPF. This results in smooth text animation.
Rich Typography
The OpenType font format is an extension of the TrueType® font format. The OpenType font format was developed jointly by Microsoft and Adobe, and provides a rich assortment of advanced typographic features. The Typography object exposes many of the advanced features of OpenType fonts, such as stylistic alternates and swashes. The Windows SDK provides a set of sample OpenType fonts that are designed with rich features, such as the Pericles and Pescadero fonts. For more information, see Sample OpenType Font Pack.
The Pericles OpenType font contains additional glyphs that provide stylistic alternates to the standard set of glyphs. The following text displays stylistic alternate glyphs.
Swashes are decorative glyphs that use elaborate ornamentation often associated with calligraphy. The following text displays standard and swash glyphs for the Pescadero font.
For more details on OpenType features, see OpenType Font Features.
Enhanced International Text Support
WPF provides enhanced international text support by providing the following features:
Automatic line-spacing in all writing systems, using adaptive measurement.
Broad support for international text. For more information, see Globalization for WPF.
Language-guided line breaking, hyphenation, and justification.
Enhanced Font Support
WPF provides enhanced font support by providing the following features:
Unicode for all text. Font behavior and selection no longer require charset or codepage.
Font behavior independent of global settings, such as system locale.
Separate FontWeight, FontStretch, and FontStyle types for defining a FontFamily. This provides greater flexibility than in Win32 programming, in which Boolean combinations of italic and bold are used to define a font family.
Writing direction (horizontal versus vertical) handled independent of font name.
Font linking and font fallback in a portable XML file, using composite font technology. Composite fonts allow for the construction of full range multilingual fonts. Composite fonts also provide a mechanism that avoids displaying missing glyphs. For more information, see the remarks in the FontFamily class.
International fonts built from composite fonts, using a group of single-language fonts. This saves on resource costs when developing fonts for multiple languages.
Composite fonts embedded in a document, thereby providing document portability. For more information, see the remarks in the FontFamilyclass.
New Text Application Programming Interfaces (APIs)
WPF provides several text APIs for developers to use when including text in their applications. These APIs are grouped into three categories:
Layout and user interface. The common text controls for the graphical user interface (GUI).
Lightweight text drawing. Allows you to draw text directly to objects.
Advanced text formatting. Allows you to implement a custom text engine.
Layout and User Interface
At the highest level of functionality, the text APIs provide common user interface (UI) controls such as Label, TextBlock, and TextBox. These controls provide the basic UI elements within an application, and offer an easy way to present and interact with text. Controls such as RichTextBox and PasswordBox enable more advanced or specialized text-handling. And classes such as TextRange, TextSelection, and TextPointer enable useful text manipulation. These UI controls provide properties such as FontFamily, FontSize, and FontStyle, which enable you to control the font that is used to render the text.
Using Bitmap Effects, Transforms, and Text Effects
WPF allows you to create visually interesting uses of text by uses features such as bitmap effects, transforms, and text effects. The following example shows a typical type of a drop shadow effect applied to text.
The following example shows a drop shadow effect and noise applied to text.
The following example shows an outer glow effect applied to text.
The following example shows a blur effect applied to text.
The following example shows the second line of text scaled by 150% along the x-axis, and the third line of text scaled by 150% along the y-axis.
The following example shows text skewed along the x-axis.
A TextEffect object is a helper object that allows you to treat text as one or more groups of characters in a text string. The following example shows an individual character being rotated. Each character is rotated independently at 1-second intervals.
Using Flow Documents
In addition to the common UI controls, WPF offers a layout control for text presentation—the FlowDocument element. The FlowDocument element, in conjunction with the DocumentViewer element, provides a control for large amounts of text with varying layout requirements. Layout controls provide access to advanced typography through the Typography object and font-related properties of other UI controls.
The following example shows text content hosted in a FlowDocumentReader, which provides search, navigation, pagination, and content scaling support.
For more information, see Documents in WPF.
Lightweight Text Drawing
You can draw text directly to WPF objects by using the DrawText method of the DrawingContext object. To use this method, you create a FormattedText object. This object allows you to draw multi-line text, in which each character in the text can be individually formatted. The functionality of the FormattedText object contains much of the functionality of the DrawText flags in the Win32 API. In addition, the FormattedTextobject contains functionality such as ellipsis support, in which an ellipsis is displayed when text exceeds its bounds. The following example shows text that has several formats applied to it, including a linear gradient on the second and third words.
You can convert formatted text into Geometry objects, allowing you to create other types of visually interesting text. For example, you could create a Geometry object based on the outline of a text string.
The following examples illustrate several ways of creating interesting visual effects by modifying the stroke, fill, and highlight of converted text.
For more information on the FormattedText object, see Drawing Formatted Text.
Advanced Text Formatting
At the most advanced level of the text APIs, WPF offers you the ability to create custom text layout by using the TextFormatter object and other types in the System.Windows.Media.TextFormatting namespace. The TextFormatter and associated classes allow you to implement custom text layout that supports your own definition of character formats, paragraph styles, line breaking rules, and other layout features for international text. There are very few cases in which you would want to override the default implementation of the WPF text layout support. However, if you were creating a text editing control or application, you might require a different implementation than the default WPF implementation.
Unlike a traditional text API, the TextFormatter interacts with a text layout client through a set of callback methods. It requires the client to provide these methods in an implementation of the TextSource class. The following diagram illustrates the text layout interaction between the client application and TextFormatter.
For more details on creating custom text layout, see Advanced Text Formatting.
ClearType Overview
This topic provides an overview of the Microsoft ClearType technology found in the Windows Presentation Foundation (WPF).
Technology Overview
ClearType is a software technology developed by Microsoft that improves the readability of text on existing LCDs (Liquid Crystal Displays), such as laptop screens, Pocket PC screens and flat panel monitors. ClearType works by accessing the individual vertical color stripe elements in every pixel of an LCD screen. Before ClearType, the smallest level of detail that a computer could display was a single pixel, but with ClearType running on an LCD monitor, we can now display features of text as small as a fraction of a pixel in width. The extra resolution increases the sharpness of the tiny details in text display, making it much easier to read over long durations.
The ClearType available in Windows Presentation Foundation (WPF) is the latest generation of ClearType which has several improvements over version found in Microsoft Windows Graphics Device Interface (GDI).
Sub-pixel Positioning
A significant improvement over the previous version of ClearType is the use of sub-pixel positioning. Unlike the ClearType implementation found in GDI, the ClearType found in Windows Presentation Foundation (WPF) allows glyphs to start within the pixel and not just the beginning boundary of the pixel. Because of this extra resolution in positioning glyphs, the spacing and proportions of the glyphs is more precise and consistent.
The following two examples show how glyphs may begin on any sub-pixel boundary when sub-pixel positioning is used. The example on the left is rendered using the earlier version of the ClearType renderer, which did not employ sub-pixel positioning. The example on the right is rendered using the new version of the ClearType renderer, using sub-pixel positioning. Note how each e and l in the right-hand image is rendered slightly differently because each starts on a different sub-pixel. When viewing the text at its normal size on the screen, this difference is not noticeable because of the high contrast of the glyph image. This is only possible because of sophisticated color filtering that is incorporated in ClearType.
The following two examples compare output from the earlier ClearType renderer with the new version of the ClearType renderer. The subpixel positioning, shown on the right, greatly improves the spacing of type on screen, especially at small sizes where the difference between a sub-pixel and a whole pixel represents a significant proportion of glyph width. Note that spacing between the letters is more even in the second image. The cumulative benefit of sub-pixel positioning to the overall appearance of a screen of text is greatly increased, and represents a significant evolution in ClearType technology.
Y-Direction Antialiasing
Another improvement of ClearType in Windows Presentation Foundation (WPF) is y-direction anti-aliasing. The ClearType in GDI without y-direction anti-aliasing provides better resolution on the x-axis but not the y-axis. On the tops and bottoms of shallow curves, the jagged edges detract from its readability.
The following example shows the effect of having no y-direction antialiasing. In this case, the jagged edges on the top and bottom of the letter are apparent.
ClearType in Windows Presentation Foundation (WPF) provides antialiasing on the y-direction level to smooth out any jagged edges. This is particularly important for improving the readability of East Asian languages where ideographs have an almost equal amount of horizontal and vertical shallow curves.
The following example shows the effect of y-direction antialiasing. In this case, the top and bottom of the letter show a smooth curve.
Hardware Acceleration
ClearType in Windows Presentation Foundation (WPF) can take advantage of hardware acceleration for better performance and to reduce CPU load and system memory requirements. By using the pixel shaders and video memory of a graphics card, ClearType provides faster rendering of text, particularly when animation is used.
ClearType in Windows Presentation Foundation (WPF) does not modify the system-wide ClearType settings. Disabling ClearType in Windows sets Windows Presentation Foundation (WPF) antialiasing to grayscale mode. In addition, ClearType in Windows Presentation Foundation (WPF) does not modify the settings of the ClearType Tuner PowerToy.
One of the Windows Presentation Foundation (WPF) architectural design decisions is to have resolution independent layout better support higher resolution DPI monitors, which are becoming more widespread. This has the consequence of Windows Presentation Foundation (WPF) not supporting aliased text rendering or the bitmaps in some East Asian fonts because they are both resolution dependent.
ClearType Registry Settings
This topic provides an overview of the WPFMicrosoft ClearType registry settings that are used by WPF applications.
Technology Overview
WPF applications that render text to a display device use ClearType features to provide an enhanced reading experience. ClearType is a software technology developed by Microsoft that improves the readability of text on existing LCDs (Liquid Crystal Displays), such as laptop screens, Pocket PC screens and flat panel monitors. ClearType works by accessing the individual vertical color stripe elements in every pixel of an LCD screen. For more information on ClearType, see ClearType Overview.
Text that is rendered with ClearType can appear significantly different when viewed on various display devices. For example, a small number of monitors implement the color stripe elements in blue, green, red order rather than the more common red, green, blue ( RGB) order.
Text that is rendered with ClearType can also appear significantly different when viewed by individuals with varying levels of color sensitivity. Some individuals can detect slight differences in color better than others.
In each of these cases, ClearType features need to be modified in order to provide the best reading experience for each individual.
Registry Settings
WPF specifies four registry settings for controlling ClearType features:
Setting | Description |
---|---|
ClearType level | Describes the level of ClearType color clarity. |
Gamma level | Describes the level of the pixel color component for a display device. |
Pixel structure | Describes the arrangement of pixels for a display device. |
Text contrast level | Describes the level of contrast for displayed text. |
These settings can be accessed by an external configuration utility that knows how to reference the identified WPFClearType registry settings. These settings can also be created or modified by accessing the values directly by using the Windows Registry Editor.
If the WPFClearType registry settings are not set (which is the default state), the WPF application queries the Windows system parameters information for font smoothing settings.
Note |
---|
For information on enumerating display device names, see the SystemParametersInfoWin32 function. |
ClearType Level
The ClearType level allows you to adjust the rendering of text based on the color sensitivity and perception of an individual. For some individuals, the rendering of text that uses ClearType at its highest level does not produce the best reading experience.
The ClearType level is an integer value that ranges from 0 to 100. The default level is 100, which means ClearType uses the maximum capability of the color stripe elements of the display device. However, a ClearType level of 0 renders text as gray scale. By setting the ClearType level somewhere between 0 and 100, you can create an intermediate level that is suitable to an individual's color sensitivity.
Registry Setting
The registry setting location for the ClearType level is an individual user setting that corresponds to a specific display device name:
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Avalon.Graphics\<displayName>
For each display device name for a user, a ClearTypeLevel DWORD value is defined. The following screenshot shows the Registry Editor setting for the ClearType level.
Note |
---|
WPF applications render text in one of either two modes, with and without ClearType. When text is rendered without ClearType, it is referred to as gray scale rendering. |
Gamma Level
The gamma level refers to the nonlinear relationship between a pixel value and luminance. The gamma level setting should correspond to the physical characteristics of the display device; otherwise, distortions in rendered output may occur. For example, test may appear too wide or too narrow, or color fringes may appear on the edges of vertical stems of glyphs.
The gamma level is an integer value that ranges from 1000 to 2200. The default level is 1900.
Registry Setting
The registry setting location for the gamma level is a local machine setting that corresponds to a specific display device name:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\<displayName>
For each display device name for a user, a GammaLevel DWORD value is defined. The following screenshot shows the Registry Editor setting for the gamma level.
Pixel Structure
The pixel structure describes the type of pixels that make up a display device. The pixel structure is defined as one of three types:
Type | Value | Description |
---|---|---|
Flat | 0 | The display device has no pixel structure. This means that light sources for each color are spread equally on the pixel area—this is referred to as gray scale rendering. This is how a standard display device works. ClearType is never applied to the rendered text. |
RGB | 1 | The display device has pixels that consist of three stripes in the following order: red, green, and blue. ClearType is applied to the rendered text. |
BGR | 2 | The display device has pixels that consist of three stripes in the following order: blue, green, and red. ClearType is applied to the rendered text. Notice how the order is inverted from the RGB type. |
The pixel structure corresponds to an integer value that ranges from 0 to 2. The default level is 0, which represents a flat pixel structure.
Note |
---|
For information on enumerating display device names, see the EnumDisplayDevicesWin32 function. |
Registry Setting
The registry setting location for the pixel structure is a local machine setting that corresponds to a specific display device name:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\<displayName>
For each display device name for a user, a PixelStructure DWORD value is defined. The following screenshot shows the Registry Editor setting for the pixel structure.
Text Contrast Level
The text contrast level allows you to adjust the rendering of text based on the stem widths of glyphs. The text contrast level is an integer value that ranges from 0 to 6—the larger the integer value, the wider the stem. The default level is 1.
Registry Setting
The registry setting location for the text contrast level is an individual user setting that corresponds to a specific display device name:
HKEY_CURRENT_USER\Software\Microsoft\Avalon.Graphics\<displayName>
For each display device name for a user, a TextContrastLevel DWORD value is defined. The following screenshot shows the Registry Editor setting for the text contrast level.
Drawing Formatted Text
This topic provides an overview of the features of the FormattedText object. This object provides low-level control for drawing text in Windows Presentation Foundation (WPF) applications.
Technology Overview
The FormattedText object allows you to draw multi-line text, in which each character in the text can be individually formatted. The following example shows text that has several formats applied to it.
Note |
---|
For those developers migrating from the Win32 API, the table in the Win32 Migration section lists the Win32 DrawText flags and the approximate equivalent in Windows Presentation Foundation (WPF). |
Reasons for Using Formatted Text
WPF includes multiple controls for drawing text to the screen. Each control is targeted to a different scenario and has its own list of features and limitations. In general, the TextBlock element should be used when limited text support is required, such as a brief sentence in a user interface (UI). Label can be used when minimal text support is required. For more information, see Documents in WPF.
The FormattedText object provides greater text formatting features than Windows Presentation Foundation (WPF) text controls, and can be useful in cases where you want to use text as a decorative element. For more information, see the following section Converting Formatted Text to a Geometry.
In addition, the FormattedText object is useful for creating text-oriented DrawingVisual-derived objects. DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. For more information, see Hit Test Using DrawingVisuals Sample.
Using the FormattedText Object
To create formatted text, call the FormattedText constructor to create a FormattedText object. Once you have created the initial formatted text string, you can apply a range of formatting styles.
Use the MaxTextWidth property to constrain the text to a specific width. The text will automatically wrap to avoid exceeding the specified width. Use the MaxTextHeight property to constrain the text to a specific height. The text will display an ellipsis, "…" for the text that exceeds the specified height.
You can apply multiple formatting styles to one or more characters. For example, you could call both the SetFontSize and SetForegroundBrushmethods to change the formatting of the first five characters in the text.
The following code example creates a FormattedText object and then applies several formatting styles to the text.
protected override void OnRender(DrawingContext drawingContext) { string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor"; // Create the initial formatted text string. FormattedText formattedText = new FormattedText( testString, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 32, Brushes.Black); // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears. formattedText.MaxTextWidth = 300; formattedText.MaxTextHeight = 240; // Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters. // The font size is calculated in terms of points -- not as device-independent pixels. formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5); // Use a Bold font weight beginning at the 6th character and continuing for 11 characters. formattedText.SetFontWeight(FontWeights.Bold, 6, 11); // Use a linear gradient brush beginning at the 6th character and continuing for 11 characters. formattedText.SetForegroundBrush( new LinearGradientBrush( Colors.Orange, Colors.Teal, 90.0), 6, 11); // Use an Italic font style beginning at the 28th character and continuing for 28 characters. formattedText.SetFontStyle(FontStyles.Italic, 28, 28); // Draw the formatted text string to the DrawingContext of the control. drawingContext.DrawText(formattedText, new Point(10, 0)); }
Font Size Unit of Measure
As with other text objects in Windows Presentation Foundation (WPF) applications, the FormattedText object uses device-independent pixels as the unit of measure. However, most Win32 applications use points as the unit of measure. If you want to use display text in units of points in Windows Presentation Foundation (WPF) applications, you need to convert device-independent units (1/96th inch per unit) to points. The following code example shows how to perform this conversion.
Converting Formatted Text to a Geometry
You can convert formatted text into Geometry objects, allowing you to create other types of visually interesting text. For example, you could create a Geometry object based on the outline of a text string.
The following examples illustrate several ways of creating interesting visual effects by modifying the stroke, fill, and highlight of converted text.
When text is converted to a Geometry object, it is no longer a collection of characters—you cannot modify the characters in the text string. However, you can affect the appearance of the converted text by modifying its stroke and fill properties. The stroke refers to the outline of the converted text; the fill refers to the area inside the outline of the converted text. For more information, see How to: Create Outlined Text.
You can also convert formatted text to a PathGeometry object, and use the object for highlighting the text. For example, you could apply an animation to the PathGeometry object so that the animation follows the outline of the formatted text.
The following example shows formatted text that has been converted to a PathGeometry object. An animated ellipse follows the path of the strokes of the rendered text.
For more information, see How to: Create a PathGeometry Animation for Text.
You can create other interesting uses for formatted text once it has been converted to a PathGeometry object. For example, you can clip video to display inside it.
Win32 Migration
The features of FormattedText for drawing text are similar to the features of the Win32 DrawText function. For those developers migrating from the Win32 API, the following table lists the Win32 DrawText flags and the approximate equivalent in Windows Presentation Foundation (WPF).
DrawText flag | WPF equivalent | Notes |
---|---|---|
DT_BOTTOM | Use the Height property to compute an appropriate Win32 DrawText 'y' position. | |
DT_CALCRECT | Use the Height and Width properties to calculate the output rectangle. | |
DT_CENTER | Use the TextAlignment property with the value set to Center. | |
DT_EDITCONTROL | None | Not required. Space width and last line rendering are the same as in the framework edit control. |
DT_END_ELLIPSIS | Use the Trimming property with the value CharacterEllipsis. Use WordEllipsis to get Win32 DT_END_ELLIPSIS with DT_WORD_ELIPSIS end ellipsis—in this case, character ellipsis only occurs on words that do not fit on a single line. | |
DT_EXPAND_TABS | None | Not required. Tabs are automatically expanded to stops every 4 ems, which is approximately the width of 8 language-independent characters. |
DT_EXTERNALLEADING | None | Not required. External leading is always included in line spacing. Use the LineHeight property to create user-defined line spacing. |
DT_HIDEPREFIX | None | Not supported. Remove the '&' from the string before constructing the FormattedText object. |
DT_LEFT | This is the default text alignment. Use the TextAlignment property with the value set to Left. (WPF only) | |
DT_MODIFYSTRING | None | Not supported. |
DT_NOCLIP | Clipping does not happen automatically. If you want to clip text, use the VisualClip property. | |
DT_NOFULLWIDTHCHARBREAK | None | Not supported. |
DT_NOPREFIX | None | Not required. The '&' character in strings is always treated as a normal character. |
DT_PATHELLIPSIS | None | Use the Trimming property with the value WordEllipsis. |
DT_PREFIX | None | Not supported. If you want to use underscores for text, such as an accelerator key or link, use the SetTextDecorations method. |
DT_PREFIXONLY | None | Not supported. |
DT_RIGHT | Use the TextAlignment property with the value set to Right. (WPF only) | |
DT_RTLREADING | Set the FlowDirection property to RightToLeft. | |
DT_SINGLELINE | None | Not required. FormattedText objects behave as a single line control, unless either the MaxTextWidthproperty is set or the text contains a carriage return/line feed (CR/LF). |
DT_TABSTOP | None | No support for user-defined tab stop positions. |
DT_TOP | Not required. Top justification is the default. Other vertical positioning values can be defined by using the Height property to compute an appropriate Win32 DrawText 'y' position. | |
DT_VCENTER | Use the Height property to compute an appropriate Win32 DrawText 'y' position. | |
DT_WORDBREAK | None | Not required. Word breaking happens automatically with FormattedText objects. You cannot disable it. |
DT_WORD_ELLIPSIS | Use the Trimming property with the value WordEllipsis. |
Advanced Text Formatting
The Windows Presentation Foundation (WPF) provides a robust set of APIs for including text in your application. Layout and user interface (UI)APIs, such as TextBlock, provide the most common and general use elements for text presentation. Drawing APIs, such as GlyphRunDrawing andFormattedText, provide a means for including formatted text in drawings. At the most advanced level, WPF provides an extensible text formatting engine to control every aspect of text presentation, such as text store management, text run formatting management, and embedded object management.
This topic provides an introduction to WPF text formatting. It focuses on client implementation and use of the WPF text formatting engine.
Note |
---|
All code examples within this document can be found in the Advanced Text Formatting Sample. |
Prerequisites
This topic assumes that you are familiar with the higher level APIs used for text presentation. Most user scenarios will not require the advanced text formatting APIs discussed in this topic. For an introduction to the different text APIs, see Documents in WPF.
Advanced Text Formatting
The text layout and UI controls in WPF provide formatting properties that allow you to easily include formatted text in your application. These controls expose a number of properties to handle the presentation of text, which includes its typeface, size, and color. Under ordinary circumstances, these controls can handle the majority of text presentation in your application. However, some advanced scenarios require the control of text storage as well as text presentation. WPF provides an extensible text formatting engine for this purpose.
The advanced text formatting features found in WPF consist of a text formatting engine, a text store, text runs, and formatting properties. The text formatting engine, TextFormatter, creates lines of text to be used for presentation. This is achieved by initiating the line formatting process and calling the text formatter's FormatLine. The text formatter retrieves text runs from your text store by calling the store's GetTextRun method. TheTextRun objects are then formed into TextLine objects by the text formatter and given to your application for inspection or display.
Using the Text Formatter
TextFormatter is the WPF text formatting engine and provides services for formatting and breaking text lines. The text formatter can handle different text character formats and paragraph styles, and includes support for international text layout.
Unlike a traditional text API, the TextFormatter interacts with a text layout client through a set of callback methods. It requires the client to provide these methods in an implementation of the TextSource class. The following diagram illustrates the text layout interaction between the client application and TextFormatter.
The text formatter is used to retrieve formatted text lines from the text store, which is an implementation of TextSource. This is done by first creating an instance of the text formatter by using the Create method. This method creates an instance of the text formatter and sets the maximum line height and width values. As soon as an instance of the text formatter is created, the line creation process is started by calling the FormatLine method.TextFormatter calls back to the text source to retrieve the text and formatting parameters for the runs of text that form a line.
In the following example, the process of formatting a text store is shown. The TextFormatter object is used to retrieve text lines from the text store and then format the text line for drawing into the DrawingContext.
// Create a DrawingGroup object for storing formatted text. textDest = new DrawingGroup(); DrawingContext dc = textDest.Open(); // Update the text store. _textStore.Text = textToFormat.Text; _textStore.FontRendering = _currentRendering; // Create a TextFormatter object. TextFormatter formatter = TextFormatter.Create(); // Format each line of text from the text store and draw it. while (textStorePosition < _textStore.Text.Length) { // Create a textline from the text store using the TextFormatter object. using (TextLine myTextLine = formatter.FormatLine( _textStore, textStorePosition, 96*6, new GenericTextParagraphProperties(_currentRendering), null)) { // Draw the formatted text into the drawing context. myTextLine.Draw(dc, linePosition, InvertAxes.None); // Update the index position in the text store. textStorePosition += myTextLine.Length; // Update the line position coordinate for the displayed line. linePosition.Y += myTextLine.Height; } } // Persist the drawn text content. dc.Close(); // Display the formatted text in the DrawingGroup object. myDrawingBrush.Drawing = textDest;
Implementing the Client Text Store
When you extend the text formatting engine, you are required to implement and manage all aspects of the text store. This is not a trivial task. The text store is responsible for tracking text run properties, paragraph properties, embedded objects, and other similar content. It also provides the text formatter with individual TextRun objects which the text formatter uses to create TextLine objects.
To handle the virtualization of the text store, the text store must be derived from TextSource. TextSource defines the method the text formatter uses to retrieve text runs from the text store. GetTextRun is the method used by the text formatter to retrieve text runs used in line formatting. The call toGetTextRun is repeatedly made by the text formatter until one of the following conditions occurs:
A TextEndOfLine or a subclass is returned.
The accumulated width of text runs exceeds the maximum line width specified in either the call to create the text formatter or the call to the text formatter's FormatLine method.
A Unicode newline sequence, such as "CF", "LF", or "CRLF", is returned.
Providing Text Runs
The core of the text formatting process is the interaction between the text formatter and the text store. Your implementation of TextSource provides the text formatter with the TextRun objects and the properties with which to format the text runs. This interaction is handled by the GetTextRunmethod, which is called by the text formatter.
The following table shows some of the predefined TextRun objects.
TextRun Type | Usage |
---|---|
The specialized text run used to pass a representation of character glyphs back to the text formatter. | |
The specialized text run used to provide content in which measuring, hit testing, and drawing is done in whole, such as a button or image within the text. | |
The specialized text run used to mark the end of a line. | |
The specialized text run used to mark the end of a paragraph. | |
The specialized text run used to mark the end of a segment, such as to end the scope affected by a previous TextModifier run. | |
The specialized text run used to mark a range of hidden characters. | |
The specialized text run used to modify properties of text runs in its scope. The scope extends to the next matchingTextEndOfSegment text run, or the next TextEndOfParagraph. |
Any of the predefined TextRun objects can be subclassed. This allows your text source to provide the text formatter with text runs that include custom data.
The following example demonstrates a GetTextRun method. This text store returns TextRun objects to the text formatter for processing.
// Used by the TextFormatter object to retrieve a run of text from the text source. public override TextRun GetTextRun(int textSourceCharacterIndex) { // Make sure text source index is in bounds. if (textSourceCharacterIndex < 0) throw new ArgumentOutOfRangeException("textSourceCharacterIndex", "Value must be greater than 0."); if (textSourceCharacterIndex >= _text.Length) { return new TextEndOfParagraph(1); } // Create TextCharacters using the current font rendering properties. if (textSourceCharacterIndex < _text.Length) { return new TextCharacters( _text, textSourceCharacterIndex, _text.Length - textSourceCharacterIndex, new GenericTextRunProperties(_currentRendering)); } // Return an end-of-paragraph if no more text source. return new TextEndOfParagraph(1); }
Note |
---|
In this example, the text store provides the same text properties to all of the text. Advanced text stores would need to implement their own span management to allow individual characters to have different properties. |
TextRun objects are formatted by using properties provided by the text store. These properties come in two types, TextParagraphProperties andTextRunProperties. TextParagraphProperties handle paragraph inclusive properties such as TextAlignment and FlowDirection. TextRunProperties are properties that can be different for each text run within a paragraph, such as foreground brush, Typeface, and font size. To implement custom paragraph and custom text run property types, your application must create classes that derive from TextParagraphProperties and TextRunPropertiesrespectively.
Fonts
OpenType Font Features
This topic provides an overview of some of the key features of OpenType font technology in Windows Presentation Foundation (WPF).
OpenType Font Format
The OpenType font format is an extension of the TrueType® font format, adding support for PostScript font data. The OpenType font format was developed jointly by Microsoft and Adobe Corporation. OpenType fonts and the operating system services which support OpenType fonts provide users with a simple way to install and use fonts, whether the fonts contain TrueType outlines or CFF (PostScript) outlines.
The OpenType font format addresses the following developer challenges:
Broader multi-platform support.
Better support for international character sets.
Better protection for font data.
Smaller file sizes to make font distribution more efficient.
Broader support for advanced typographic control.
Note |
---|
The Windows SDK contains a set of sample OpenType fonts that you can use with Windows Presentation Foundation (WPF) applications. These fonts provide most of the features illustrated in the rest of this topic. For more information, see Sample OpenType Font Pack. |
See the OpenType Specification for details of the OpenType font format.
Advanced Typographic Extensions
The Advanced Typographic tables (OpenType Layout tables) extend the functionality of fonts with either TrueType or CFF outlines. OpenType Layout fonts contain additional information that extends the capabilities of the fonts to support high-quality international typography. Most OpenType fonts expose only a subset of the total OpenType features available. OpenType fonts provide the following features.
Rich mapping between characters and glyphs that support ligatures, positional forms, alternates, and other font substitutions.
Support for two-dimensional positioning and glyph attachment.
Explicit script and language information contained in font, so a text-processing application can adjust its behavior accordingly.
The OpenType Layout tables are described in more detail in the "Font File Tables" section of the OpenType specification.
The remainder of this overview introduces the breadth and flexibility of some of the visually-interesting OpenType features that are exposed by the properties of the Typography object. For more information on this object, see Typography Class.
Variants
Variants are used to render different typographic styles, such as superscripts and subscripts.
Superscripts and Subscripts
The Variants property allows you to set superscript and subscript values for an OpenType font.
The following text displays superscripts for the Palatino Linotype font.
The following markup example shows how to define superscripts for the Palatino Linotype font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype"> 2<Run Typography.Variants="Superscript">3</Run> 14<Run Typography.Variants="Superscript">th</Run> </Paragraph>
The following text displays subscripts for the Palatino Linotype font.
The following markup example shows how to define subscripts for the Palatino Linotype font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype"> H<Run Typography.Variants="Subscript">2</Run>O Footnote<Run Typography.Variants="Subscript">4</Run> </Paragraph>
Decorative Uses of Superscripts and Subscripts
You can also use superscripts and subscripts to create decorative effects of mixed case text. The following text displays superscript and subscript text for the Palatino Linotype font. Note that the capitals are not affected.
The following markup example shows how to define superscripts and subscripts for a font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype" Typography.Variants="Superscript"> Chapter One </Paragraph> <Paragraph FontFamily="Palatino Linotype" Typography.Variants="Subscript"> Chapter One </Paragraph>
Capitals
Capitals are a set of typographical forms that render text in capital-styled glyphs. Typically, when text is rendered as all capitals, the spacing between letters can appear too tight, and the weight and proportion of the letters too heavy. OpenType supports a number of styling formats for capitals, including small capitals, petite capitals, titling, and capital spacing. These styling formats allow you to control the appearance of capitals.
The following text displays standard capital letters for the Pescadero font, followed by the letters styled as "SmallCaps" and "AllSmallCaps". In this case, the same font size is used for all three words.
The following markup example shows how to define capitals for the Pescadero font, using properties of the Typography object. When the "SmallCaps" format is used, any leading capital letter is ignored.
<Paragraph FontFamily="Pescadero" FontSize="48"> <Run>CAPITALS</Run> <Run Typography.Capitals="SmallCaps">Capitals</Run> <Run Typography.Capitals="AllSmallCaps">Capitals</Run> </Paragraph>
Titling Capitals
Titling capitals are lighter in weight and proportion and designed to give a more elegant look than normal capitals. Titling capitals are typically used in larger font sizes as headings. The following text displays normal and titling capitals for the Pescadero font. Notice the narrower stem widths of the text on the second line.
The following markup example shows how to define titling capitals for the Pescadero font, using properties of the Typography object.
<Paragraph FontFamily="Pescadero"> <Run Typography.Capitals="Titling">chapter one</Run> </Paragraph>
Capital Spacing
Capital spacing is a feature that allows you to provide more spacing when using all capitals in text. Capital letters are typically designed to blend with lowercase letters. Spacing that appears attractive between and a capital letter and a lowercase letter may look too tight when all capital letters are used. The following text displays normal and capital spacing for the Pescadero font.
The following markup example shows how to define capital spacing for the Pescadero font, using properties of the Typography object.
<Paragraph FontFamily="Pescadero"> <Run Typography.CapitalSpacing="True">CHAPTER ONE</Run> </Paragraph>
Ligatures
Ligatures are two or more glyphs that are formed into a single glyph in order to create more readable or attractive text. OpenType fonts support four types of ligatures:
Standard ligatures. Designed to enhance readability. Standard ligatures include "fi", "fl", and "ff".
Contextual ligatures. Designed to enhance readability by providing better joining behavior between the characters that make up the ligature.
Discretionary ligatures. Designed to be ornamental, and not specifically designed for readability.
Historical ligatures. Designed to be historical, and not specifically designed for readability.
The following text displays standard ligature glyphs for the Pericles font.
The following markup example shows how to define standard ligature glyphs for the Pericles font, using properties of the Typography object.
<Paragraph FontFamily="Pericles" Typography.StandardLigatures="True"> <Run Typography.StylisticAlternates="1">FI</Run> <Run Typography.StylisticAlternates="1">FL</Run> <Run Typography.StylisticAlternates="1">TH</Run> <Run Typography.StylisticAlternates="1">TT</Run> <Run Typography.StylisticAlternates="1">TV</Run> <Run Typography.StylisticAlternates="1">TW</Run> <Run Typography.StylisticAlternates="1">TY</Run> <Run Typography.StylisticAlternates="1">VT</Run> <Run Typography.StylisticAlternates="1">WT</Run> <Run Typography.StylisticAlternates="1">YT</Run> </Paragraph>
The following text displays discretionary ligature glyphs for the Pericles font.
The following markup example shows how to define discretionary ligature glyphs for the Pericles font, using properties of the Typography object.
<Paragraph FontFamily="Pericles" Typography.DiscretionaryLigatures="True"> <Run Typography.StylisticAlternates="1">CO</Run> <Run Typography.StylisticAlternates="1">LA</Run> <Run Typography.StylisticAlternates="1">LE</Run> <Run Typography.StylisticAlternates="1">LI</Run> <Run Typography.StylisticAlternates="1">LL</Run> <Run Typography.StylisticAlternates="1">LO</Run> <Run Typography.StylisticAlternates="1">LU</Run> </Paragraph>
By default, OpenType fonts in Windows Presentation Foundation (WPF) enable standard ligatures. For example, if you use the Palatino Linotype font, the standard ligatures "fi", "ff", and "fl" appear as a combined character glyph. Notice that the pair of characters for each standard ligature touch each other.
However, you can disable standard ligature features so that a standard ligature such as "ff" displays as two separate glyphs, rather than as a combined character glyph.
The following markup example shows how to disable standard ligature glyphs for the Palatino Linotype font, using properties of the Typographyobject.
<!-- Set standard ligatures to false in order to disable feature. --> <Paragraph Typography.StandardLigatures="False" FontFamily="Palatino Linotype" FontSize="72"> fi ff fl </Paragraph>
Swashes
Swashes are decorative glyphs that use elaborate ornamentation often associated with calligraphy. The following text displays standard and swash glyphs for the Pescadero font.
Swashes are often used as decorative elements in short phrases such as event announcements. The following text uses swashes to emphasize the capital letters of the name of the event.
The following markup example shows how to define swashes for a font, using properties of the Typography object.
<Paragraph FontFamily="Pescadero" TextBlock.TextAlignment="Center"> Wishing you a<LineBreak/> <Run Typography.StandardSwashes="1" FontSize="36">Happy New Year!</Run> </Paragraph>
Contextual Swashes
Certain combinations of swash glyphs can cause an unattractive appearance, such as overlapping descenders on adjacent letters. Using a contextual swash allows you to use a substitute swash glyph that produces a better appearance. The following text shows the same word before and after a contextual swash is applied.
The following markup example shows how to define a contextual swash for the Pescadero font, using properties of the Typography object.
<Paragraph FontFamily="Pescadero" Typography.StandardSwashes="1"> Lyon <Run Typography.ContextualSwashes="1">L</Run>yon </Paragraph>
Alternates
Alternates are glyphs that can be substituted for a standard glyph. OpenType fonts, such as the Pericles font used in the following examples, can contain alternate glyphs that you can use to create different appearances for text. The following text displays standard glyphs for the Pericles font.
The Pericles OpenType font contains additional glyphs that provide stylistic alternates to the standard set of glyphs. The following text displays stylistic alternate glyphs.
The following markup example shows how to define stylistic alternate glyphs for the Pericles font, using properties of the Typography object.
<Paragraph FontFamily="Pericles"> <Run Typography.StylisticAlternates="1">A</Run>NCIENT GR<Run Typography.StylisticAlternates="1">EE</Run>K MYTH<Run Typography.StylisticAlternates="1">O</Run>LOGY </Paragraph>
The following text displays several other stylistic alternate glyphs for the Pericles font.
The following markup example shows how to define these other stylistic alternate glyphs.
<Paragraph FontFamily="Pericles"> <Run Typography.StylisticAlternates="1">A</Run> <Run Typography.StylisticAlternates="2">A</Run> <Run Typography.StylisticAlternates="3">A</Run> <Run Typography.StylisticAlternates="1">C</Run> <Run Typography.StylisticAlternates="1">E</Run> <Run Typography.StylisticAlternates="1">G</Run> <Run Typography.StylisticAlternates="1">O</Run> <Run Typography.StylisticAlternates="1">Q</Run> <Run Typography.StylisticAlternates="1">R</Run> <Run Typography.StylisticAlternates="2">R</Run> <Run Typography.StylisticAlternates="1">S</Run> <Run Typography.StylisticAlternates="1">Y</Run> </Paragraph>
Random Contextual Alternates
Random contextual alternates provide multiple substitute glyphs for a single character. When implemented with script-type fonts, this feature can simulate handwriting by using of a set of randomly chosen glyphs with slight differences in appearance. The following text uses random contextual alternates for the Lindsey font. Notice that the letter "a" varies slightly in appearance
The following markup example shows how to define random contextual alternates for the Lindsey font, using properties of the Typography object.
<TextBlock FontFamily="Lindsey"> <Run Typography.ContextualAlternates="True"> a banana in a cabana </Run> </TextBlock>
Historical Forms
Historical forms are typographic conventions that were common in the past. The following text displays the phrase, "Boston, Massachusetts", using an historical form of glyphs for the Palatino Linotype font.
The following markup example shows how to define historical forms for the Palatino Linotype font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype"> <Run Typography.HistoricalForms="True">Boston, Massachusetts</Run> </Paragraph>
Numerical Styles
OpenType fonts support a large number of features that can be used with numerical values in text.
Fractions
OpenType fonts support styles for fractions, including slashed and stacked.
The following text displays fraction styles for the Palatino Linotype font.
The following markup example shows how to define fraction styles for the Palatino Linotype font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype" Typography.Fraction="Slashed"> 1/8 1/4 3/8 1/2 5/8 3/4 7/8 </Paragraph> <Paragraph FontFamily="Palatino Linotype" Typography.Fraction="Stacked"> 1/8 1/4 3/8 1/2 5/8 3/4 7/8 </Paragraph>
Old Style Numerals
OpenType fonts support an old style numeral format. This format is useful for displaying numerals in styles that are no longer standard. The following text displays an 18th century date in standard and old style numeral formats for the Palatino Linotype font.
The following text displays standard numerals for the Palatino Linotype font, followed by old style numerals.
The following markup example shows how to define old style numerals for the Palatino Linotype font, using properties of the Typography object.
<Paragraph FontFamily="Palatino Linotype"> <Run Typography.NumeralStyle="Normal">1234567890</Run> <Run Typography.NumeralStyle="OldStyle">1234567890</Run> </Paragraph>
Proportional and Tabular Figures
OpenType fonts support a proportional and tabular figure feature to control the alignment of widths when using numerals. Proportional figures treat each numeral as having a different width—"1" is narrower than "5". Tabular figures are treated as equal-width numerals so that they align vertically, which increases the readability of financial type information.
The following text displays two proportional figures in the first column using the Miramonte font. Note the difference in width between the numerals "5" and "1". The second column shows the same two numeric values with the widths adjusted by using the tabular figure feature.
The following markup example shows how to define proportional and tabular figures for the Miramonte font, using properties of the Typographyobject.
<TextBlock FontFamily="Miramonte"> <Run Typography.NumeralAlignment="Proportional">114,131</Run> </TextBlock> <TextBlock FontFamily="Miramonte"> <Run Typography.NumeralAlignment="Tabular">114,131</Run> </TextBlock>
Slashed Zero
OpenType fonts support a slashed zero numeral format to emphasize the difference between the letter "O" and the numeral "0". The slashed zero numeral is often used for identifiers in financial and business information.
The following text displays a sample order identifier using the Miramonte font. The first line uses standard numerals. The second line used slashed zero numerals to provide better contrast with the uppercase "O" letter.
The following markup example shows how to define slashed zero numerals for the Miramonte font, using properties of the Typography object.
<Paragraph FontFamily="Miramonte"> <Run>Order #0048-OTC-390</Run> <LineBreak/> <Run Typography.SlashedZero="True">Order #0048-OTC-390</Run> </Paragraph>
Typography Class
The Typography object exposes the set of features that an OpenType font supports. By setting the properties of Typography in markup, you can easily author documents that take advantage of OpenType features.
The following text displays standard capital letters for the Pescadero font, followed by the letters styled as "SmallCaps" and "AllSmallCaps". In this case, the same font size is used for all three words.
The following markup example shows how to define capitals for the Pescadero font, using properties of the Typography object. When the "SmallCaps" format is used, any leading capital letter is ignored.
<Paragraph FontFamily="Pescadero" FontSize="48"> <Run>CAPITALS</Run> <Run Typography.Capitals="SmallCaps">Capitals</Run> <Run Typography.Capitals="AllSmallCaps">Capitals</Run> </Paragraph>
The following code example accomplishes the same task as the previous markup example.
MyParagraph.FontFamily = new FontFamily("Pescadero"); MyParagraph.FontSize = 48; Run run_1 = new Run("CAPITALS "); MyParagraph.Inlines.Add(run_1); Run run_2 = new Run("Capitals "); run_2.Typography.Capitals = FontCapitals.SmallCaps; MyParagraph.Inlines.Add(run_2); Run run_3 = new Run("Capitals"); run_3.Typography.Capitals = FontCapitals.AllSmallCaps; MyParagraph.Inlines.Add(run_3); MyParagraph.Inlines.Add(new LineBreak());
Typography Class Properties
The following table lists the properties, values, and default settings of the Typography object.
Property | Value(s) | Default Value |
---|---|---|
Numeric value - byte | 0 | |
AllPetiteCaps | AllSmallCaps | Normal | PetiteCaps | SmallCaps | Titling | Unicase | ||
false | ||
false | ||
true | ||
true | ||
Numeric value - byte | 0 | |
false | ||
false | ||
HojoKanji | Jis04 | Jis78 | Jis83 | Jis90 | NlcKanji | Normal | Simplified | Traditional | TraditionalNames | ||
false | ||
false | ||
true | ||
false | ||
false | ||
true | ||
numeric value – byte | 0 | |
numeric value – byte | 0 | |
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
false | ||
Inferior | Normal | Ordinal | Ruby | Subscript | Superscript |
Packaging Fonts with Applications
This topic provides an overview of how to package fonts with your Windows Presentation Foundation (WPF) application.
Note |
---|
As with most types of software, font files are licensed, rather than sold. Licenses that govern the use of fonts vary from vendor to vendor but in general most licenses, including those covering the fonts Microsoft supplies with applications and Windows, do not allow the fonts to be embedded within applications or otherwise redistributed. Therefore, as a developer it is your responsibility to ensure that you have the required license rights for any font you embed within an application or otherwise redistribute. |
Introduction to Packaging Fonts
You can easily package fonts as resources within your WPF applications to display user interface text and other types of text based content. The fonts can be separate from or embedded within the application's assembly files. You can also create a resource-only font library, which your application can reference.
OpenType and TrueType® fonts contain a type flag, fsType, that indicates font embedding licensing rights for the font. However, this type flag only refers to embedded fonts stored in a document–it does not refer to fonts embedded in an application. You can retrieve the font embedding rights for a font by creating a GlyphTypeface object and referencing its EmbeddingRights property. Refer to the "OS/2 and Windows Metrics" section of theOpenType Specification for more information on the fsType flag.
The Microsoft Typography Web site includes contact information that can help you locate a particular font vendor or find a font vendor for custom work.
Adding Fonts as Content Items
You can add fonts to your application as project content items that are separate from the application's assembly files. This means that content items are not embedded as resources within an assembly. The following project file example shows how to define content items.
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Other project build settings ... --> <ItemGroup> <Content Include="Peric.ttf" /> <Content Include="Pericl.ttf" /> </ItemGroup> </Project>
In order to ensure that the application can use the fonts at run time, the fonts must be accessible in the application's deployment directory. The<CopyToOutputDirectory> element in the application's project file allows you to automatically copy the fonts to the application deployment directory during the build process. The following project file example shows how to copy fonts to the deployment directory.
<ItemGroup> <Content Include="Peric.ttf"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="Pericl.ttf"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup>
The following code example shows how to reference the application's font as a content item—the referenced content item must be in the same directory as the application's assembly files.
<TextBlock FontFamily="./#Pericles Light"> Aegean Sea </TextBlock>
Adding Fonts as Resource Items
You can add fonts to your application as project resource items that are embedded within the application's assembly files. Using a separate subdirectory for resources helps to organize the application's project files. The following project file example shows how to define fonts as resource items in a separate subdirectory.
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Other project build settings ... --> <ItemGroup> <Resource Include="resources\Peric.ttf" /> <Resource Include="resources\Pericl.ttf" /> </ItemGroup> </Project>
Note |
---|
When you add fonts as resources to your application, make sure you are setting the <Resource> element, and not the <EmbeddedResource>element in your application's project file. The <EmbeddedResource> element for the build action is not supported. |
The following markup example shows how to reference the application's font resources.
<TextBlock FontFamily="./resources/#Pericles Light"> Aegean Sea </TextBlock>
Referencing Font Resource Items from Code
In order to reference font resource items from code, you must supply a two-part font resource reference: the base uniform resource identifier (URI); and the font location reference. These values are used as the parameters for the FontFamily method. The following code example shows how to reference the application's font resources in the project subdirectory called resources.
// The font resource reference includes the base URI reference (application directory level), // and a relative URI reference. myTextBlock.FontFamily = new FontFamily(new Uri("pack://application:,,,/"), "./resources/#Pericles Light");
The base uniform resource identifier (URI) can include the application subdirectory where the font resource resides. In this case, the font location reference would not need to specify a directory, but would have to include a leading " ./", which indicates the font resource is in the same directory specified by the base uniform resource identifier (URI). The following code example shows an alternate way of referencing the font resource item—it is equivalent to the previous code example.
Referencing Fonts from the Same Application Subdirectory
You can place both application content and resource files within the same user-defined subdirectory of your application project. The following project file example shows a content page and font resources defined in the same subdirectory.
<ItemGroup> <Page Include="pages\HomePage.xaml" /> </ItemGroup> <ItemGroup> <Resource Include="pages\Peric.ttf" /> <Resource Include="pages\Pericl.ttf" /> </ItemGroup>
Since the application content and font are in the same subdirectory, the font reference is relative to the application content. The following examples show how to reference the application's font resource when the font is in the same directory as the application.
<TextBlock FontFamily="./#Pericles Light"> Aegean Sea </TextBlock>
Enumerating Fonts in an Application
To enumerate fonts as resource items in your application, use either the GetFontFamilies or GetTypefaces method. The following example shows how to use the GetFontFamilies method to return the collection of FontFamily objects from the application font location. In this case, the application contains a subdirectory named "resources".
foreach (FontFamily fontFamily in Fonts.GetFontFamilies(new Uri("pack://application:,,,/"), "./resources/")) { // Perform action. }
The following example shows how to use the GetTypefaces method to return the collection of Typeface objects from the application font location. In this case, the application contains a subdirectory named "resources".
Creating a Font Resource Library
You can create a resource-only library that contains only fonts—no code is part of this type of library project. Creating a resource-only library is a common technique for decoupling resources from the application code that uses them. This also allows the library assembly to be included with multiple application projects. The following project file example shows the key portions of a resource-only library project.
<PropertyGroup> <AssemblyName>FontLibrary</AssemblyName> <OutputType>library</OutputType> ... </PropertyGroup> ... <ItemGroup> <Resource Include="Kooten.ttf" /> <Resource Include="Pesca.ttf" /> </ItemGroup
Referencing a Font in a Resource Library
To reference a font in a resource library from your application, you must prefix the font reference with the name of the library assembly. In this case, the font resource assembly is "FontLibrary". To separate the assembly name from the reference within the assembly, use a ';' character. Adding the "Component" keyword followed by the reference to the font name completes the full reference to the font library's resource. The following code example shows how to reference a font in a resource library assembly.
<Run FontFamily="/FontLibrary;Component/#Kootenay" FontSize="36"> ABCDEFGHIJKLMNOPQRSTUVWXYZ </Run>
Note |
---|
This SDK contains a set of sample OpenType fonts that you can use with WPF applications. The fonts are defined in a resource-only library. For more information, see Sample OpenType Font Pack. |
Limitations on Font Usage
The following list describes several limitations on the packaging and use of fonts in WPF applications:
Font embedding permission bits:WPF applications do not check or enforce any font embedding permission bits. See theIntroduction_to_Packing Fonts section for more information.
Site of origin fonts:WPF applications do not allow a font reference to an http or ftp uniform resource identifier (URI).
Absolute URI using the pack: notation:WPF applications do not allow you to create a FontFamily object programmatically using "pack:" as part of the absolute uniform resource identifier (URI) reference to a font. For example, "pack://application:,,,/resources/#Pericles Light" is an invalid font reference.
Automatic font embedding: During design time, there is no support for searching an application's use of fonts and automatically embedding the fonts in the application's resources.
Font subsets:WPF applications do not support the creation of font subsets for non-fixed documents.
In cases where there is an incorrect reference, the application falls back to using an available font.
Sample OpenType Font Pack
This topic provides an overview of the sample OpenType fonts that are distributed with the Windows SDK. The sample fonts support extended OpenType features that can be used by Windows Presentation Foundation (WPF) applications.
Fonts in the OpenType Font Pack
The Windows SDK provides a set of sample OpenType fonts that you can use in creating Windows Presentation Foundation (WPF) applications. The sample fonts are supplied under license from Ascender Corporation. These fonts implement only a subset of the total features defined by the OpenType format. The following table lists the names of the sample OpenType fonts.
Name | File |
---|---|
Kootenay | Kooten.ttf |
Lindsey | Linds.ttf |
Miramonte | Miramo.ttf |
Miramonte Bold | Miramob.ttf |
Pericles | Peric.ttf |
Pericles Light | Pericl.ttf |
Pescadero | Pesca.ttf |
Pescadero Bold | Pescab.ttf |
The following illustration shows what the sample OpenType fonts look like.
The sample fonts are supplied under license from Ascender Corporation. Ascender is a provider of advanced font products. To license extended or custom versions of the sample fonts, see Ascender Corporation's Web site.
Note |
---|
As a developer it is your responsibility to ensure that you have the required license rights for any font you embed within an application or otherwise redistribute. |
Installing the Fonts
You have the option of installing the sample OpenType fonts to the default Windows Fonts directory, \WINDOWS\Fonts. Use the Fonts control panel to install the fonts. Once these fonts are on your computer, they are accessible to all applications that reference default Windows fonts. You can display a representative set of characters in several font sizes by doubling-clicking the font file. The following screenshot shows the Lindsey font file, Linds.ttf.
There are two ways that you can use fonts in your application. You can add fonts to your application as project content items that are not embedded as resources within an assembly. Alternatively, you can add fonts to your application as project resource items that are embedded within the application's assembly files. For more information, see Packaging Fonts with Applications.
Fonts How-to Topics
How to: Enumerate System Fonts
Example
The following example shows how to enumerate the fonts in the system font collection. The font family name of each FontFamily within SystemFontFamilies is added as an item to a combo box.
public void FillFontComboBox(ComboBox comboBoxFonts) { // Enumerate the current set of system fonts, // and fill the combo box with the names of the fonts. foreach (FontFamily fontFamily in Fonts.SystemFontFamilies) { // FontFamily.Source contains the font family name. comboBoxFonts.Items.Add(fontFamily.Source); } comboBoxFonts.SelectedIndex = 0; }
If multiple versions of the same font family reside in the same directory, the Windows Presentation Foundation (WPF) font enumeration returns the most recent version of the font. If the version information does not provide resolution, the font with latest timestamp is returned. If the timestamp information is equivalent, the font file that is first in alphabetical order is returned.
How to: Use the FontSizeConverter Class
Example
This example shows how to create an instance of FontSizeConverter and use it to change a font size.
The example defines a custom method called changeSize that converts the contents of a ListBoxItem, as defined in a separate Extensible Application Markup Language (XAML) file, to an instance of Double, and later into a String. This method passes the ListBoxItem to a FontSizeConverter object, which converts the Content of a ListBoxItem to an instance of Double. This value is then passed back as the value of the FontSize property of theTextBlock element.
This example also defines a second custom method that is called changeFamily. This method converts the Content of the ListBoxItem to a String, and then passes that value to the FontFamily property of the TextBlock element.
This example does not run.
private void changeSize(object sender, SelectionChangedEventArgs args) { ListBoxItem li = ((sender as ListBox).SelectedItem as ListBoxItem); FontSizeConverter myFontSizeConverter = new FontSizeConverter(); text1.FontSize = (Double)myFontSizeConverter.ConvertFromString(li.Content.ToString()); } private void changeFamily(object sender, SelectionChangedEventArgs args) { ListBoxItem li2 = ((sender as ListBox).SelectedItem as ListBoxItem); text1.FontFamily = new System.Windows.Media.FontFamily(li2.Content.ToString()); }
Glyphs
Introduction to the GlyphRun Object and Glyphs Element
Introduction to GlyphRun
Windows Presentation Foundation (WPF) provides advanced text support including glyph-level markup with direct access to Glyphs for customers who want to intercept and persist text after formatting. These features provide critical support for the different text rendering requirements in each of the following scenarios.
Screen display of fixed-format documents.
Print scenarios.
Extensible Application Markup Language (XAML) as a device printer language.
Microsoft XPS Document Writer.
Previous printer drivers, output from Win32 applications to the fixed format.
Print spool format.
Fixed-format document representation, including clients for previous versions of Windows and other computing devices.
Note |
---|
Glyphs and GlyphRun are designed for fixed-format document presentation and print scenarios. Windows Presentation Foundation (WPF) provides several elements for general layout and user interface (UI) scenarios such as Label and TextBlock. For more information on layout and UI scenarios, see the Typography in WPF. |
The GlyphRun Object
The GlyphRun object represents a sequence of glyphs from a single face of a single font at a single size, and with a single rendering style.
GlyphRun includes both font details such as glyph Indices and individual glyph positions. It also includes the original Unicode code points the run was generated from, character-to-glyph buffer offset mapping information, and per-character and per-glyph flags.
GlyphRun has a corresponding high-level FrameworkElement, Glyphs. Glyphs can be used in the element tree and in XAML markup to representGlyphRun output.
The Glyphs Element
The Glyphs element represents the output of a GlyphRun in XAML. The following markup syntax is used to describe the Glyphs element.
<!-- The example shows how to use a Glyphs object. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <StackPanel Background="PowderBlue"> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "100" StyleSimulations = "BoldSimulation" UnicodeString = "Hello World!" Fill = "Black" OriginX = "100" OriginY = "200" /> </StackPanel> </Page>
The following property definitions correspond to the first four attributes in the sample markup.
Property | Description |
---|---|
Specifies a resource identifier: file name, Web uniform resource identifier (URI), or resource reference in the application .exe or container. | |
Specifies the font size in drawing surface units (default is .96 inches). | |
Specifies flags for bold and Italic styles. | |
Specifies the bidirectional layout level. Even-numbered and zero values imply left-to-right layout; odd-numbered values imply right-to-left layout. |
Indices property
The Indices property is a string of glyph specifications. Where a sequence of glyphs forms a single cluster, the specification of the first glyph in the cluster is preceded by a specification of how many glyphs and how many code points combine to form the cluster. The Indices property collects in one string the following properties.
Glyph indices
Glyph advance widths
Combining glyph attachment vectors
Cluster mapping from code points to glyphs
Glyph flags
Each glyph specification has the following form.
[GlyphIndex][,[Advance][,[uOffset][,[vOffset][,[Flags]]]]]
Glyph Metrics
Each glyph defines metrics that specify how it aligns with other Glyphs. The following graphic defines the various typographic qualities of two different glyph characters.
Glyphs Markup
The following code example shows how to use various properties of the Glyphs element in XAML.
<!-- The example shows how to use different property settings of Glyphs objects. --> <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="PowderBlue" > <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" StyleSimulations = "ItalicSimulation" UnicodeString = "Hello World!" Fill = "SteelBlue" OriginX = "50" OriginY = "75" /> <!-- "Hello World!" with default kerning --> <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" UnicodeString = "Hello World!" Fill = "Maroon" OriginX = "50" OriginY = "150" /> <!-- "Hello World!" with explicit character widths for proportional font --> <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" UnicodeString = "Hello World!" Indices = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80" Fill = "Maroon" OriginX = "50" OriginY = "225" /> <!-- "Hello World!" with fixed-width font --> <Glyphs FontUri = "C:\WINDOWS\Fonts\COUR.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Hello World!" Fill = "Maroon" OriginX = "50" OriginY = "300" /> <!-- "Open file" without "fi" ligature --> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Open file" Fill = "SlateGray" OriginX = "400" OriginY = "75" /> <!-- "Open file" with "fi" ligature --> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Open file" Indices = ";;;;;(2:1)191" Fill = "SlateGray" OriginX = "400" OriginY = "150" /> </Canvas>
Draw Text Using Glyphs
This topic explains how to use the low-level Glyphs object to display text in Extensible Application Markup Language (XAML).
Example
The following examples show how to define properties for a Glyphs object in Extensible Application Markup Language (XAML). The Glyphs object represents the output of a GlyphRun in XAML. The examples assume that the Arial, Courier New, and Times New Roman fonts are installed in the C:\WINDOWS\Fonts folder on the local computer.
<!-- The example shows how to use a Glyphs object. --> <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <StackPanel Background="PowderBlue"> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "100" StyleSimulations = "BoldSimulation" UnicodeString = "Hello World!" Fill = "Black" OriginX = "100" OriginY = "200" /> </StackPanel> </Page>
This example shows how to define other properties of Glyphs objects in XAML.
<!-- The example shows how to use different property settings of Glyphs objects. --> <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="PowderBlue" > <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" StyleSimulations = "ItalicSimulation" UnicodeString = "Hello World!" Fill = "SteelBlue" OriginX = "50" OriginY = "75" /> <!-- "Hello World!" with default kerning --> <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" UnicodeString = "Hello World!" Fill = "Maroon" OriginX = "50" OriginY = "150" /> <!-- "Hello World!" with explicit character widths for proportional font --> <Glyphs FontUri = "C:\WINDOWS\Fonts\ARIAL.TTF" FontRenderingEmSize = "36" UnicodeString = "Hello World!" Indices = ",80;,80;,80;,80;,80;,80;,80;,80;,80;,80;,80" Fill = "Maroon" OriginX = "50" OriginY = "225" /> <!-- "Hello World!" with fixed-width font --> <Glyphs FontUri = "C:\WINDOWS\Fonts\COUR.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Hello World!" Fill = "Maroon" OriginX = "50" OriginY = "300" /> <!-- "Open file" without "fi" ligature --> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Open file" Fill = "SlateGray" OriginX = "400" OriginY = "75" /> <!-- "Open file" with "fi" ligature --> <Glyphs FontUri = "C:\WINDOWS\Fonts\TIMES.TTF" FontRenderingEmSize = "36" StyleSimulations = "BoldSimulation" UnicodeString = "Open file" Indices = ";;;;;(2:1)191" Fill = "SlateGray" OriginX = "400" OriginY = "150" /> </Canvas>
Typography How-to Topics
How to: Create a Text Decoration
A TextDecoration object is a visual ornamentation you can add to text. There are four types of text decorations: underline, baseline, strikethrough, and overline. The following example shows the locations of the text decorations relative to the text.
To add a text decoration to text, create a TextDecoration object and modify its properties. Use the Location property to specify where the text decoration appears, such as underline. Use the Pen property to specify the appearance of the text decoration, such as a solid fill or gradient color. If you do not specify a value for the Pen property, the decorations defaults to the same color as the text. Once you have defined a TextDecoration object, add it to the TextDecorations collection of the desired text object.
The following example shows a text decoration that has been styled with a linear gradient brush and a dashed pen.
The Hyperlink object is an inline-level flow content element that allows you to host hyperlinks within the flow content. By default, Hyperlink uses a TextDecoration object to display an underline. TextDecoration objects can be performance intensive to instantiate, particularly if you have many Hyperlink objects. If you make extensive use of Hyperlink elements, you may want to consider showing an underline only when triggering an event, such as the MouseEnter event.
In the following example, the underline for the "My MSN" link is dynamic—it only appears when the MouseEnter event is triggered.
For more information, see How to: Specify Whether a Hyperlink is Underlined.
Example
In the following code example, an underline text decoration uses the default font value.
// Use the default font values for the strikethrough text decoration. private void SetDefaultStrikethrough() { // Set the underline decoration directly to the text block. TextBlock1.TextDecorations = TextDecorations.Strikethrough; }
<!-- Use the default font values for the strikethrough text decoration. --> <TextBlock TextDecorations="Strikethrough" FontSize="36" > The quick red fox </TextBlock>
In the following code example, an underline text decoration is created with a solid color brush for the pen.
// Use a Red pen for the underline text decoration. private void SetRedUnderline() { // Create an underline text decoration. Default is underline. TextDecoration myUnderline = new TextDecoration(); // Create a solid color brush pen for the text decoration. myUnderline.Pen = new Pen(Brushes.Red, 1); myUnderline.PenThicknessUnit = TextDecorationUnit.FontRecommended; // Set the underline decoration to a TextDecorationCollection and add it to the text block. TextDecorationCollection myCollection = new TextDecorationCollection(); myCollection.Add(myUnderline); TextBlock2.TextDecorations = myCollection; }
<!-- Use a Red pen for the underline text decoration --> <TextBlock FontSize="36" > jumped over <TextBlock.TextDecorations> <TextDecorationCollection> <TextDecoration PenThicknessUnit="FontRecommended"> <TextDecoration.Pen> <Pen Brush="Red" Thickness="1" /> </TextDecoration.Pen> </TextDecoration> </TextDecorationCollection> </TextBlock.TextDecorations> </TextBlock>
In the following code example, an underline text decoration is created with a linear gradient brush for the dashed pen.
// Use a linear gradient pen for the underline text decoration. private void SetLinearGradientUnderline() { // Create an underline text decoration. Default is underline. TextDecoration myUnderline = new TextDecoration(); // Create a linear gradient pen for the text decoration. Pen myPen = new Pen(); myPen.Brush = new LinearGradientBrush(Colors.Yellow, Colors.Red, new Point(0, 0.5), new Point(1, 0.5)); myPen.Brush.Opacity = 0.5; myPen.Thickness = 1.5; myPen.DashStyle = DashStyles.Dash; myUnderline.Pen = myPen; myUnderline.PenThicknessUnit = TextDecorationUnit.FontRecommended; // Set the underline decoration to a TextDecorationCollection and add it to the text block. TextDecorationCollection myCollection = new TextDecorationCollection(); myCollection.Add(myUnderline); TextBlock3.TextDecorations = myCollection; }
<!-- Use a linear gradient pen for the underline text decoration. --> <TextBlock FontSize="36">the lazy brown dog. <TextBlock.TextDecorations> <TextDecorationCollection> <TextDecoration PenThicknessUnit="FontRecommended"> <TextDecoration.Pen> <Pen Thickness="1.5"> <Pen.Brush> <LinearGradientBrush Opacity="0.5" StartPoint="0,0.5" EndPoint="1,0.5"> <LinearGradientBrush.GradientStops> <GradientStop Color="Yellow" Offset="0" /> <GradientStop Color="Red" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Pen.Brush> <Pen.DashStyle> <DashStyle Dashes="2"/> </Pen.DashStyle> </Pen> </TextDecoration.Pen> </TextDecoration> </TextDecorationCollection> </TextBlock.TextDecorations> </TextBlock>
How to: Specify Whether a Hyperlink is Underlined
The Hyperlink object is an inline-level flow content element that allows you to host hyperlinks within the flow content. By default, Hyperlink uses a TextDecoration object to display an underline. TextDecoration objects can be performance intensive to instantiate, particularly if you have many Hyperlink objects. If you make extensive use of Hyperlink elements, you may want to consider showing an underline only when triggering an event, such as the MouseEnter event.
In the following example, the underline for the "My MSN" link is dynamic—it only appears when the MouseEnter event is triggered.
Example
The following markup sample shows a Hyperlink defined with and without an underline:
<!-- Hyperlink with default underline. --> <Hyperlink NavigateUri="http://www.msn.com"> MSN Home </Hyperlink> <Run Text=" | " /> <!-- Hyperlink with no underline. --> <Hyperlink Name="myHyperlink" TextDecorations="None" MouseEnter="OnMouseEnter" MouseLeave="OnMouseLeave" NavigateUri="http://www.msn.com"> My MSN </Hyperlink>
The following code sample shows how to create an underline for the Hyperlink on the MouseEnter event, and remove it on the MouseLeave event.
// Display the underline on only the MouseEnter event. private void OnMouseEnter(object sender, EventArgs e) { myHyperlink.TextDecorations = TextDecorations.Underline; } // Remove the underline on the MouseLeave event. private void OnMouseLeave(object sender, EventArgs e) { myHyperlink.TextDecorations = null; }
How to: Apply Transforms to Text
Transforms can alter the display of text in your application. The following examples use different types of rendering transforms to affect the display of text in a TextBlock control.
Example
The following example shows text rotated about a specified point in the two-dimensional x-y plane.
The following code example uses a RotateTransform to rotate text. An Angle value of 90 rotates the element 90 degrees clockwise.
<!-- Rotate the text 90 degrees using a RotateTransform. --> <TextBlock FontFamily="Arial Black" FontSize="64" Foreground="Moccasin" Margin ="80, 10, 0, 0"> Text Transforms <TextBlock.RenderTransform> <RotateTransform Angle="90" /> </TextBlock.RenderTransform> </TextBlock>
The following example shows the second line of text scaled by 150% along the x-axis, and the third line of text scaled by 150% along the y-axis.
The following code example uses a ScaleTransform to scale text from its original size.
<!-- Scale the text using a ScaleTransform. --> <TextBlock Name="textblockScaleMaster" FontSize="32" Foreground="SteelBlue" Text="Scaled Text" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="0"> </TextBlock> <TextBlock FontSize="32" FontWeight="Bold" Foreground="SteelBlue" Text="{Binding Path=Text, ElementName=textblockScaleMaster}" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="1"> <TextBlock.RenderTransform> <ScaleTransform ScaleX="1.5" ScaleY="1.0" /> </TextBlock.RenderTransform> </TextBlock> <TextBlock FontSize="32" FontWeight="Bold" Foreground="SteelBlue" Text="{Binding Path=Text, ElementName=textblockScaleMaster}" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="2"> <TextBlock.RenderTransform> <ScaleTransform ScaleX="1.0" ScaleY="1.5" /> </TextBlock.RenderTransform> </TextBlock>
Note |
---|
Scaling text is not the same as increasing the font size of text. Font sizes are calculated independently of each other in order to provide the best resolution at different sizes. Scaled text, on the other hand, preserves the proportions of the original-sized text. |
The following example shows text skewed along the x-axis.
The following code example uses a SkewTransform to skew text. A skew, also known as a shear, is a transformation that stretches the coordinate space in a non-uniform manner. In this example, the two text strings are skewed -30° and 30° along the x-coordinate.
<!-- Skew the text using a SkewTransform. --> <TextBlock Name="textblockSkewMaster" FontSize="32" FontWeight="Bold" Foreground="Maroon" Text="Skewed Text" Margin="125, 0, 0, 0" Grid.Column="0" Grid.Row="0"> <TextBlock.RenderTransform> <SkewTransform AngleX="-30" AngleY="0" /> </TextBlock.RenderTransform> </TextBlock> <TextBlock FontSize="32" FontWeight="Bold" Foreground="Maroon" Text="{Binding Path=Text, ElementName=textblockSkewMaster}" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="1"> <TextBlock.RenderTransform> <SkewTransform AngleX="30" AngleY="0" /> </TextBlock.RenderTransform> </TextBlock>
The following example shows text translated, or moved, along the x- and y-axis.
The following code example uses a TranslateTransform to offset text. In this example, a slightly offset copy of text below the primary text creates a shadow effect.
<!-- Skew the text using a TranslateTransform. --> <TextBlock FontSize="32" FontWeight="Bold" Foreground="Black" Text="{Binding Path=Text, ElementName=textblockTranslateMaster}" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="0"> <TextBlock.RenderTransform> <TranslateTransform X="2" Y="2" /> </TextBlock.RenderTransform> </TextBlock> <TextBlock Name="textblockTranslateMaster" FontSize="32" FontWeight="Bold" Foreground="Coral" Text="Translated Text" Margin="100, 0, 0, 0" Grid.Column="0" Grid.Row="0"/>
Note |
---|
The DropShadowBitmapEffect provides a rich set of features for providing shadow effects. For more information, see How to: Create Text with a Shadow. |
How to: Apply Animations to Text
Animations can alter the display and appearance of text in your application. The following examples use different types of animations to affect the display of text in a TextBlock control.
Example
The following example uses a DoubleAnimation to animate the width of the text block. The width value changes from the width of the text block to 0 over a duration of 10 seconds, and then reverses the width values and continues. This type of animation creates a wipe effect.
<TextBlock Name="MyWipedText" Margin="20" Width="480" Height="100" FontSize="48" FontWeight="Bold" Foreground="Maroon"> This is wiped text <!-- Animates the text block's width. --> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyWipedText" Storyboard.TargetProperty="(TextBlock.Width)" To="0.0" Duration="0:0:10" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
The following example uses a DoubleAnimation to animate the opacity of the text block. The opacity value changes from 1.0 to 0 over a duration of 5 seconds, and then reverses the opacity values and continues.
<TextBlock Name="MyFadingText" Margin="20" Width="640" Height="100" FontSize="48" FontWeight="Bold" Foreground="Maroon"> This is fading text <!-- Animates the text block's opacity. --> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyFadingText" Storyboard.TargetProperty="(TextBlock.Opacity)" From="1.0" To="0.0" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
The following diagram shows the effect of the TextBlock control changing its opacity from 1.00 to 0.00 during the 5-second interval defined by theDuration.
The following example uses a ColorAnimation to animate the foreground color of the text block. The foreground color value changes from one color to a second color over a duration of 5 seconds, and then reverses the color values and continues.
<TextBlock Name="MyChangingColorText" Margin="20" Width="640" Height="100" FontSize="48" FontWeight="Bold"> This is changing color text <TextBlock.Foreground> <SolidColorBrush x:Name="MySolidColorBrush" Color="Maroon" /> </TextBlock.Foreground> <!-- Animates the text block's color. --> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="MySolidColorBrush" Storyboard.TargetProperty="Color" From="DarkOrange" To="SteelBlue" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
The following example uses a DoubleAnimation to rotate the text block. The text block performs a full rotation over a duration of 20 seconds, and then continues to repeat the rotation.
<TextBlock Name="MyRotatingText" Margin="20" Width="640" Height="100" FontSize="48" FontWeight="Bold" Foreground="Teal" > This is rotating text <TextBlock.RenderTransform> <RotateTransform x:Name="MyRotateTransform" Angle="0" CenterX="230" CenterY="25"/> </TextBlock.RenderTransform> <!-- Animates the text block's rotation. --> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="MyRotateTransform" Storyboard.TargetProperty="(RotateTransform.Angle)" From="0.0" To="360" Duration="0:0:10" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
How to: Create Text with a Shadow
The examples in this section show how to create a shadow effect for displayed text.
Example
The DropShadowEffect object allows you to create a variety of drop shadow effects for Windows Presentation Foundation (WPF) objects. The following example shows a drop shadow effect applied to text. In this case, the shadow is a soft shadow, which means the shadow color blurs.
You can control the width of a shadow by setting the ShadowDepth property. A value of 4.0 indicates a shadow width of 4 pixels. You can control the softness, or blur, of a shadow by modifying the BlurRadius property. A value of 0.0 indicates no blurring. The following code example shows how to create a soft shadow.
<!-- Soft single shadow. --> <TextBlock Text="Shadow Text" Foreground="Teal"> <TextBlock.Effect> <DropShadowEffect ShadowDepth="4" Direction="330" Color="Black" Opacity="0.5" BlurRadius="4"/> </TextBlock.Effect> </TextBlock>
Note |
---|
These shadow effects do not go through the Windows Presentation Foundation (WPF) text rendering pipeline. As a result, ClearType is disabled when using these effects. |
The following example shows a hard drop shadow effect applied to text. In this case, the shadow is not blurred.
You can create a hard shadow by setting the BlurRadius property to 0.0, which indicates that no blurring is used. You can control the direction of the shadow by modifying the Direction property. Set the directional value of this property to a degree between 0 and 360. The following illustration shows the directional values of the Direction property setting.
The following code example shows how to create a hard shadow.
<!-- Hard single shadow. --> <TextBlock Text="Shadow Text" Foreground="Maroon"> <TextBlock.Effect> <DropShadowEffect ShadowDepth="6" Direction="135" Color="Maroon" Opacity="0.35" BlurRadius="0.0" /> </TextBlock.Effect> </TextBlock>
Using a Blur Effect
A BlurBitmapEffect can be used to create a shadow-like effect that can be placed behind a text object. A blur bitmap effect applied to text blurs the text evenly in all directions.
The following example shows a blur effect applied to text.
The following code example shows how to create a blur effect.
<!-- Shadow effect by creating a blur. --> <TextBlock Text="Shadow Text" Foreground="Green" Grid.Column="0" Grid.Row="0" > <TextBlock.Effect> <BlurEffect Radius="8.0" KernelType="Box"/> </TextBlock.Effect> </TextBlock> <TextBlock Text="Shadow Text" Foreground="Maroon" Grid.Column="0" Grid.Row="0" />
Using a Translate Transform
A TranslateTransform can be used to create a shadow-like effect that can be placed behind a text object.
The following code example uses a TranslateTransform to offset text. In this example, a slightly offset copy of text below the primary text creates a shadow effect.
The following code example shows how to create a transform for a shadow effect.
<!-- Shadow effect by creating a transform. --> <TextBlock Foreground="Black" Text="Shadow Text" Grid.Column="0" Grid.Row="0"> <TextBlock.RenderTransform> <TranslateTransform X="3" Y="3" /> </TextBlock.RenderTransform> </TextBlock> <TextBlock Foreground="Coral" Text="Shadow Text" Grid.Column="0" Grid.Row="0"> </TextBlock>
How to: Create Outlined Text
In most cases, when you are adding ornamentation to text strings in your Windows Presentation Foundation (WPF) application, you are using text in terms of a collection of discrete characters, or glyphs. For example, you could create a linear gradient brush and apply it to the Foreground property of a TextBox object. When you display or edit the text box, the linear gradient brush is automatically applied to the current set of characters in the text string.
However, you can also convert text into Geometry objects, allowing you to create other types of visually rich text. For example, you could create a Geometry object based on the outline of a text string.
When text is converted to a Geometry object, it is no longer a collection of characters—you cannot modify the characters in the text string. However, you can affect the appearance of the converted text by modifying its stroke and fill properties. The stroke refers to the outline of the converted text; the fill refers to the area inside the outline of the converted text.
The following examples illustrate several ways of creating visual effects by modifying the stroke and fill of converted text.
It is also possible to modify the bounding box rectangle, or highlight, of the converted text. The following example illustrates a way to creating visual effects by modifying the stroke and highlight of converted text.
Example
The key to converting text to a Geometry object is to use the FormattedText object. Once you have created this object, you can use the BuildGeometryand BuildHighlightGeometry methods to convert the text to Geometry objects. The first method returns the geometry of the formatted text; the second method returns the geometry of the formatted text's bounding box. The following code example shows how to create a FormattedText object and to retrieve the geometries of the formatted text and its bounding box.
/// <summary> /// Create the outline geometry based on the formatted text. /// </summary> public void CreateText() { System.Windows.FontStyle fontStyle = FontStyles.Normal; FontWeight fontWeight = FontWeights.Medium; if (Bold == true) fontWeight = FontWeights.Bold; if (Italic == true) fontStyle = FontStyles.Italic; // Create the formatted text based on the properties set. FormattedText formattedText = new FormattedText( Text, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface( Font, fontStyle, fontWeight, FontStretches.Normal), FontSize, System.Windows.Media.Brushes.Black // This brush does not matter since we use the geometry of the text. ); // Build the geometry object that represents the text. _textGeometry = formattedText.BuildGeometry(new System.Windows.Point(0, 0)); // Build the geometry object that represents the text hightlight. if (Highlight == true) { _textHighLightGeometry = formattedText.BuildHighlightGeometry(new System.Windows.Point(0, 0)); } }
In order to display the retrieved the Geometry objects, you need to access the DrawingContext of the object that is displaying the converted text. In these code examples, this is done by creating a custom control object that is derived from a class that supports user-defined rendering.
To display Geometry objects in the custom control, provide an override for the OnRender method. Your overridden method should use the DrawGeometry method to draw the Geometry objects.
/// <summary> /// OnRender override draws the geometry of the text and optional highlight. /// </summary> /// <param name="drawingContext">Drawing context of the OutlineText control.</param> protected override void OnRender(DrawingContext drawingContext) { // Draw the outline based on the properties that are set. drawingContext.DrawGeometry(Fill, new System.Windows.Media.Pen(Stroke, StrokeThickness), _textGeometry); // Draw the text highlight based on the properties that are set. if (Highlight == true) { drawingContext.DrawGeometry(null, new System.Windows.Media.Pen(Stroke, StrokeThickness), _textHighLightGeometry); } }
How to: Draw Text to a Control's Background
You can draw text directly to the background of a control by converting a text string to a FormattedText object, and then drawing the object to the control's DrawingContext. You can also use this technique for drawing to the background of objects derived from Panel, such as Canvas and StackPanel.
Example
To draw to the background of a control, create a new DrawingBrush object and draw the converted text to the object's DrawingContext. Then, assign the new DrawingBrush to the control's background property.
The following code example shows how to create a FormattedText object and draw to the background of a Label and Button object.
// Handle the WindowLoaded event for the window. private void WindowLoaded(object sender, EventArgs e) { // Update the background property of the label and button. myLabel.Background = new DrawingBrush(DrawMyText("My Custom Label")); myButton.Background = new DrawingBrush(DrawMyText("Display Text")); } // Convert the text string to a geometry and draw it to the control's DrawingContext. private Drawing DrawMyText(string textString) { // Create a new DrawingGroup of the control. DrawingGroup drawingGroup = new DrawingGroup(); // Open the DrawingGroup in order to access the DrawingContext. using (DrawingContext drawingContext = drawingGroup.Open()) { // Create the formatted text based on the properties set. FormattedText formattedText = new FormattedText( textString, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Comic Sans MS Bold"), 48, System.Windows.Media.Brushes.Black // This brush does not matter since we use the geometry of the text. ); // Build the geometry object that represents the text. Geometry textGeometry = formattedText.BuildGeometry(new System.Windows.Point(20, 0)); // Draw a rounded rectangle under the text that is slightly larger than the text. drawingContext.DrawRoundedRectangle(System.Windows.Media.Brushes.PapayaWhip, null, new Rect(new System.Windows.Size(formattedText.Width + 50, formattedText.Height + 5)), 5.0, 5.0); // Draw the outline based on the properties that are set. drawingContext.DrawGeometry(System.Windows.Media.Brushes.Gold, new System.Windows.Media.Pen(System.Windows.Media.Brushes.Maroon, 1.5), textGeometry); // Return the updated DrawingGroup content to be used by the control. return drawingGroup; } }
How to: Draw Text to a Visual
The following example shows how to draw text to a DrawingVisual using a DrawingContext object. A drawing context is returned by calling the RenderOpen method of a DrawingVisual object. You can draw graphics and text into a drawing context.
To draw text into the drawing context, use the DrawText method of a DrawingContext object. When you are finished drawing content into the drawing context, call the Close method to close the drawing context and persist the content.
Example
// Create a DrawingVisual that contains text. private DrawingVisual CreateDrawingVisualText() { // Create an instance of a DrawingVisual. DrawingVisual drawingVisual = new DrawingVisual(); // Retrieve the DrawingContext from the DrawingVisual. DrawingContext drawingContext = drawingVisual.RenderOpen(); // Draw a formatted text string into the DrawingContext. drawingContext.DrawText( new FormattedText("Click Me!", CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 36, System.Windows.Media.Brushes.Black), new System.Windows.Point(200, 116)); // Close the DrawingContext to persist changes to the DrawingVisual. drawingContext.Close(); return drawingVisual; }
Note |
---|
For the complete code sample from which the preceding code example was extracted, see Hit Test Using DrawingVisuals Sample. |
How to: Use Special Characters in XAML
Markup files that are created in Microsoft Visual Studio are automatically saved in the Unicode UTF-8 file format, which means that most special characters, such as accent marks, are encoded correctly. However, there is a set of commonly-used special characters that are handled differently. These special characters follow the World Wide Web Consortium (W3C)XML standard for encoding.
The following table shows the syntax for encoding this set of special characters:
Character | Syntax | Description |
---|---|---|
< | < | Less than symbol. |
> | > | Greater than sign. |
& | & | Ampersand symbol. |
" | " | Double quote symbol. |
Note |
---|
If you create a markup file using a text editor, such as Windows Notepad, you must save the file in the Unicode UTF-8 file format in order to preserve any encoded special characters. |
The following example shows how you can use special characters in text when creating markup.
Example
<!-- Display special characters that require special encoding: < > & " --> <TextBlock> < <!-- Less than symbol --> > <!-- Greater than symbol --> & <!-- Ampersand symbol --> " <!-- Double quote symbol --> </TextBlock> <!-- Display miscellaneous special characters --> <TextBlock> Cæsar <!-- AE dipthong symbol --> © 2006 <!-- Copyright symbol --> Español <!-- Tilde symbol --> ¥ <!-- Yen symbol --> </TextBlock>
Printing and Print System Management
With Microsoft .NET Framework, application developers using Windows Presentation Foundation (WPF) have a rich new set of printing and print system management APIs. With Windows Vista, some of these print system enhancements are also available to developers creating Windows Forms applications and developers using unmanaged code. At the core of this new functionality is the new XML Paper Specification (XPS) file format and the XPS print path.
This topic contains the following sections.
About XPS
XPS is an electronic document format, a spool file format and a page description language. It is an open document format that uses XML, Open Packaging Conventions (OPC), and other industry standards to create cross-platform documents. XPS simplifies the process by which digital documents are created, shared, printed, viewed, and archived. For additional information on XPS, see the XPS Web Site.
Several techniques for printing XPS based content using WPF are demonstrated in How to: Programmatically Print XPS Files. You may find it useful to reference these samples during review of content contained in this topic. (Unmanaged code developers should see the help for the Microsoft XPS Document Converter printer escape. Windows Forms developers must use the API in the System.Drawing.Printing namespace which does not support the full XPS print path, but does support a hybrid GDI-to-XPS print path. See Print Path Architecture below.)
XPS Print Path
The XML Paper Specification (XPS) print path is a new Windows feature that redefines how printing is handled in Windows applications. Because XPS can replace a document presentation language (such as RTF), a print spooler format (such as WMF), and a page description language (such as PCL or Postscript); the new print path maintains the XPS format from application publication to the final processing in the print driver or device.
The XPS print path is built upon the XPS printer driver model (XPSDrv), which provides several benefits for developers such as "what you see is what you get" (WYSIWYG) printing, improved color support, and significantly improved print performance. (For more on XPSDrv, see the Windows Driver Development Kit.)
The operation of the print spooler for XPS documents is essentially the same as in previous versions of Windows. However, it has been enhanced to support the XPS print path in addition to the existing GDI print path. The new print path natively consumes an XPS spool file. While user-mode printer drivers written for previous versions of Windows will continue to work, an XPS printer driver (XPSDrv) is required in order to use the XPS print path.
The benefits of the XPS print path are significant, and include:
WYSIWYG print support
Native support of advanced color profiles, which include 32 bits per channel (bpc), CMYK, named-colors, n-inks, and native support of transparency and gradients.
Improved print performance for both .NET Framework and Win32 based applications.
Industry standard XPS format.
For basic print scenarios, a simple and intuitive API is available with a single entry point for user interface, configuration and job submission. For advanced scenarios, an additional support is added for user interface (UI) customization (or no UI at all), synchronous or asynchronous printing, and batch printing capabilities. Both options provide print support in full or partial trust mode.
XPS was designed with extensibility in mind. By using the extensibility framework, features and capabilities can be added to XPS in a modular manner. Extensibility features include:
Print Schema. The public schema is updated regularly and enables rapid extension of device capabilities. (See PrintTicket and PrintCapabilities below.)
Extensible Filter Pipeline. The XPS printer driver (XPSDrv) filter pipeline was designed to enable both direct and scalable printing of XPS documents. (Lookup "XPSDrv" in the Windows Driver Development Kit.)
Print Path Architecture
While both Win32 and .NET Framework applications support XPS, Win32 and Windows Forms applications use a GDI to XPS conversion in order to create XPS formatted content for the XPS printer driver (XPSDrv). These applications are not required to use the XPS print path, and can continue to use Enhanced Metafile (EMF) based printing. However, most XPS features and enhancements are only available to applications that target the XPS print path.
To enable the use of XPSDrv-based printers by Win32 and Windows Forms applications, the XPS printer driver (XPSDrv) supports conversion of GDI to XPS format. The XPSDrv model also provides a converter for XPS to GDI format so that Win32 applications can print XPS Documents. For WPF applications, conversion of XPS to GDI format is done automatically by the Write and WriteAsync methods of the XpsDocumentWriter class whenever the target print queue of the write operation does not have an XPSDrv driver. (Windows Forms applications cannot print XPS Documents.)
The following illustration depicts the print subsystem and defines the portions provided by Microsoft, and the portions defined by software and hardware vendors.
Basic XPS Printing
WPF defines both a basic and advanced API. For those applications that do not require extensive print customization or access to the complete XPS feature set, basic print support is available. Basic print support is exposed through a print dialog control that requires minimal configuration and features a familiar UI. Many XPS features are available using this simplified print model.
PrintDialog
The System.Windows.Controls.PrintDialog control provides a single entry point for UI, configuration, and XPS job submission. For information about how to instantiate and use the control, see How to: Invoke a Print Dialog.
Advanced XPS Printing
To access the complete set of XPS features, the advanced print API must be used. Several relevant API are described in greater detail below. For a complete list of XPS print path APIs, see the System.Windows.Xps and System.Printing namespace references.
PrintTicket and PrintCapabilities
The PrintTicket and PrintCapabilities classes are the foundation of the advanced XPS features. Both types of objects are XML formatted structures of print-oriented features such as collation, two-sided printing, stapling, etc. These structures are defined by the print schema. A PrintTicket instructs a printer how to process a print job. The PrintCapabilities class defines the capabilities of a printer. By querying the capabilities of a printer, a PrintTicket can be created that takes full advantage of a printer's supported features. Similarly, unsupported features can be avoided.
The following example demonstrates how to query the PrintCapabilities of a printer and create a PrintTicket using code.
// ---------------------- GetPrintTicketFromPrinter ----------------------- /// <summary> /// Returns a PrintTicket based on the current default printer.</summary> /// <returns> /// A PrintTicket for the current local default printer.</returns> private PrintTicket GetPrintTicketFromPrinter() { PrintQueue printQueue = null; LocalPrintServer localPrintServer = new LocalPrintServer(); // Retrieving collection of local printer on user machine PrintQueueCollection localPrinterCollection = localPrintServer.GetPrintQueues(); System.Collections.IEnumerator localPrinterEnumerator = localPrinterCollection.GetEnumerator(); if (localPrinterEnumerator.MoveNext()) { // Get PrintQueue from first available printer printQueue = (PrintQueue)localPrinterEnumerator.Current; } else { // No printer exist, return null PrintTicket return null; } // Get default PrintTicket from printer PrintTicket printTicket = printQueue.DefaultPrintTicket; PrintCapabilities printCapabilites = printQueue.GetPrintCapabilities(); // Modify PrintTicket if (printCapabilites.CollationCapability.Contains(Collation.Collated)) { printTicket.Collation = Collation.Collated; } if ( printCapabilites.DuplexingCapability.Contains( Duplexing.TwoSidedLongEdge) ) { printTicket.Duplexing = Duplexing.TwoSidedLongEdge; } if (printCapabilites.StaplingCapability.Contains(Stapling.StapleDualLeft)) { printTicket.Stapling = Stapling.StapleDualLeft; } return printTicket; }// end:GetPrintTicketFromPrinter()
PrintServer and PrintQueue
The PrintServer class represents a network print server and the PrintQueue class represents a printer and the output job queue associated with it. Together, these APIs allow advanced management of a server's print jobs. A PrintServer, or one of its derived classes, is used to manage a PrintQueue. The AddJob method is used to insert a new print job into the queue.
The following example demonstrates how to create a LocalPrintServer and access its default PrintQueue by using code.
// -------------------- GetPrintXpsDocumentWriter() ------------------- /// <summary> /// Returns an XpsDocumentWriter for the default print queue.</summary> /// <returns> /// An XpsDocumentWriter for the default print queue.</returns> private XpsDocumentWriter GetPrintXpsDocumentWriter() { // Create a local print server LocalPrintServer ps = new LocalPrintServer(); // Get the default print queue PrintQueue pq = ps.DefaultPrintQueue; // Get an XpsDocumentWriter for the default print queue XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(pq); return xpsdw; }// end:GetPrintXpsDocumentWriter()
XpsDocumentWriter
An XpsDocumentWriter, with its many the Write and WriteAsync methods, is used to write XPS documents to a PrintQueue. For example, the Write(FixedPage, PrintTicket) method is used to output an XPS document and PrintTicket synchronously. The WriteAsync(FixedDocument, PrintTicket) method is used to output an XPS document and PrintTicket asynchronously.
The following example demonstrates how to create an XpsDocumentWriter using code.
// -------------------- GetPrintXpsDocumentWriter() ------------------- /// <summary> /// Returns an XpsDocumentWriter for the default print queue.</summary> /// <returns> /// An XpsDocumentWriter for the default print queue.</returns> private XpsDocumentWriter GetPrintXpsDocumentWriter() { // Create a local print server LocalPrintServer ps = new LocalPrintServer(); // Get the default print queue PrintQueue pq = ps.DefaultPrintQueue; // Get an XpsDocumentWriter for the default print queue XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(pq); return xpsdw; }// end:GetPrintXpsDocumentWriter()
The AddJob methods also provide ways to print. See How to: Programmatically Print XPS Files. for details.
GDI Print Path
While WPF applications natively support the XPS print path, Win32 and Windows Forms applications can also take advantage of some XPS features. The XPS printer driver (XPSDrv) can convert GDI based output to XPS format. For advanced scenarios, custom conversion of content is supported using the Microsoft XPS Document Converter printer escape. Similarly, WPF applications can also output to the GDI print path by calling one of the Write or WriteAsync methods of the XpsDocumentWriter class and designating a non-XpsDrv printer as the target print queue.
For applications that do not require XPS functionality or support, the current GDI print path remains unchanged.
For additional reference material on the GDI print path and the various XPS conversion options, see Microsoft XPS Document Converter printer escape and "XPSDrv" in the Windows Driver Development Kit.
XPSDrv Driver Model
The XPS print path improves spooler efficiency by using XPS as the native print spool format when printing to an XPS -enabled printer or driver. The simplified spooling process eliminates the need to generate an intermediate spool file, such as an EMF data file, before the document is spooled. Through smaller spool file sizes, the XPS print path can reduce network traffic and improve print performance.
EMF is a closed format that represents application output as a series of calls into GDI for rendering services. Unlike EMF, the XPS spool format represents the actual document without requiring further interpretation when output to an XPS-based printer driver (XPSDrv). The drivers can operate directly on the data in the format. This capability eliminates the data and color space conversions required when you use EMF files and GDI-based print drivers.
Spool file sizes are usually reduced when you use XPS Documents that target an XPS printer driver (XPSDrv) compared with their EMF equivalents; however, there are exceptions:
A vector graphic that is very complex, multi-layered, or inefficiently written can be larger than a bitmapped version of the same graphic.
For screen display purposes, XPS files embed device fonts as well as computer-based fonts; whereas GDI spool files do not embed device fonts. But both kinds of fonts are subsetted (see below) and printer drivers can remove the device fonts before transmitting the file to the printer.
Spool size reduction is performed through several mechanisms:
Font subsetting. Only characters used within the actual document are stored in the XPS file.
Advanced Graphics Support. Native support for transparency and gradient primitives avoids rasterization of content in the XPS Document.
Identification of common resources. Resources that are used multiple times (such as an image that represents a corporate logo) are treated as shared resources and are loaded only once.
ZIP compression. All XPS documents use ZIP compression.
Printing How-to Topics
To provide the ability to print from you application, you can simply create and open a PrintDialog object.
Example
The PrintDialog control provides a single entry point for UI, configuration, and XPS job submission. The control is easy to use and can be instantiated by using Extensible Application Markup Language (XAML) markup or code. The following example demonstrates how to instantiate and open the control in code and how to print from it. It also shows how to ensure that the dialog will give the user the option of setting a specific range of pages. The example code assumes that there is a file FixedDocumentSequence.xps in the root of the C: drive.
private void InvokePrint(object sender, RoutedEventArgs e) { // Create the print dialog object and set options PrintDialog pDialog = new PrintDialog(); pDialog.PageRangeSelection = PageRangeSelection.AllPages; pDialog.UserPageRangeEnabled = true; // Display the dialog. This returns true if the user presses the Print button. Nullable<Boolean> print = pDialog.ShowDialog(); if (print == true) { XpsDocument xpsDocument = new XpsDocument("C:\\FixedDocumentSequence.xps", FileAccess.ReadWrite); FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence(); pDialog.PrintDocument(fixedDocSeq.DocumentPaginator, "Test print job"); } }
Once the dialog is open, users will be able to select from the printers installed on their computer. They will also have the option of selecting the Microsoft XPS Document Writer to create an XML Paper Specification (XPS) file instead of printing.
Note |
---|
The System.Windows.Controls.PrintDialog control of WPF, which is discussed in this topic, should not be confused with the System.Windows.Forms.PrintDialog component of Windows Forms. |
Strictly speaking, you can use the PrintDocument method without ever opening the dialog. In that sense, the control can be used as an unseen printing component. But for performance reasons, it would be better to use either the AddJob method or one of the many Write and WriteAsync methods of the XpsDocumentWriter. For more about this, see How to: Programmatically Print XPS Files and .
How to: Clone a Printer
Most businesses will, at some point, buy multiple printers of the same model. Typically, these are all installed with virtually identical configuration settings. Installing each printer can be time-consuming and error prone. The System.Printing.IndexedProperties namespace and the InstallPrintQueueclass that are exposed with Microsoft .NET Framework makes it possible to instantly install any number of additional print queues that are cloned from an existing print queue.
Example
In the example below, a second print queue is cloned from an existing print queue. The second differs from the first only in its name, location, port, and shared status. The major steps for doing this are as follows.
Create a PrintQueue object for the existing printer that is going to be cloned.
Create a PrintPropertyDictionary from the PropertiesCollection of the PrintQueue. The Value property of each entry in this dictionary is an object of one of the types derived from PrintProperty. There are two ways to set the value of an entry in this dictionary.
Use the dictionary's Remove and Add methods to remove the entry and then re-add it with the desired value.
Use the dictionary's SetProperty method.
The example below illustrates both ways.
Create a PrintBooleanProperty object and set its Name to "IsShared" and its Value to true.
Use the PrintBooleanProperty object to be the value of the PrintPropertyDictionary's "IsShared" entry.
Create a PrintStringProperty object and set its Name to "ShareName" and its Value to an appropriate String.
Use the PrintStringProperty object to be the value of the PrintPropertyDictionary's "ShareName" entry.
Create another PrintStringProperty object and set its Name to "Location" and its Value to an appropriate String.
Use the second PrintStringProperty object to be the value of the PrintPropertyDictionary's "Location" entry.
Create an array of Strings. Each item is the name of a port on the server.
Use InstallPrintQueue to install the new printer with the new values.
An example is below.
LocalPrintServer myLocalPrintServer = new LocalPrintServer(PrintSystemDesiredAccess.AdministrateServer); PrintQueue sourcePrintQueue = myLocalPrintServer.DefaultPrintQueue; PrintPropertyDictionary myPrintProperties = sourcePrintQueue.PropertiesCollection; // Share the new printer using Remove/Add methods PrintBooleanProperty shared = new PrintBooleanProperty("IsShared", true); myPrintProperties.Remove("IsShared"); myPrintProperties.Add("IsShared", shared); // Give the new printer its share name using SetProperty method PrintStringProperty theShareName = new PrintStringProperty("ShareName", "\"Son of " + sourcePrintQueue.Name +"\""); myPrintProperties.SetProperty("ShareName", theShareName); // Specify the physical location of the new printer using Remove/Add methods PrintStringProperty theLocation = new PrintStringProperty("Location", "the supply room"); myPrintProperties.Remove("Location"); myPrintProperties.Add("Location", theLocation); // Specify the port for the new printer String[] port = new String[] { "COM1:" }; // Install the new printer on the local print server PrintQueue clonedPrinter = myLocalPrintServer.InstallPrintQueue("My clone of " + sourcePrintQueue.Name, "Xerox WCP 35 PS", port, "WinPrint", myPrintProperties); myLocalPrintServer.Commit(); // Report outcome Console.WriteLine("{0} in {1} has been installed and shared as {2}", clonedPrinter.Name, clonedPrinter.Location, clonedPrinter.ShareName); Console.WriteLine("Press Return to continue ..."); Console.ReadLine();
How to: Diagnose Problematic Print Job
Network administrators often field complaints from users about print jobs that do not print or print slowly. The rich set of print job properties exposed in the APIs of Microsoft .NET Framework provide a means for performing a rapid remote diagnosis of print jobs.
Example
The major steps for creating this kind of utility are as follows.
Identify the print job that the user is complaining about. Users often cannot do this precisely. They may not know the names of the print servers or printers. They may describe the location of the printer in different terminology than was used in setting its Location property. Accordingly, it is a good idea to generate a list of the user's currently submitted jobs. If there is more than one, then communication between the user and the print system administrator can be used to pinpoint the job that is having problems. The substeps are as follows.
Obtain a list of all print servers.
Loop through the servers to query their print queues.
Within each pass of the server loop, loop through all the server's queues to query their jobs
Within each pass of the queue loop, loop through its jobs and gather identifying information about those that were submitted by the complaining user.
When the problematic print job has been identified, examine relevant properties to see what might be the problem. For example, is job in an error state or did the printer servicing the queue go offline before the job could print?
The code below is series of code examples. The first code example contains the loop through the print queues. (Step 1c above.) The variable myPrintQueues is the PrintQueueCollection object for the current print server.
The code example begins by refreshing the current print queue object with PrintQueue.Refresh. This ensures that the object's properties accurately represent the state of the physical printer that it represents. Then the application gets the collection of print jobs currently in the print queue by using GetPrintJobInfoCollection.
Next the application loops through the PrintSystemJobInfo collection and compares each Submitter property with the alias of the complaining user. If they match, the application adds identifying information about the job to the string that will be presented. (The userName and jobList variables are initialized earlier in the application.)
foreach (PrintQueue pq in myPrintQueues) { pq.Refresh(); PrintJobInfoCollection jobs = pq.GetPrintJobInfoCollection(); foreach (PrintSystemJobInfo job in jobs) { // Since the user may not be able to articulate which job is problematic, // present information about each job the user has submitted. if (job.Submitter == userName) { atLeastOne = true; jobList = jobList + "\nServer:" + line; jobList = jobList + "\n\tQueue:" + pq.Name; jobList = jobList + "\n\tLocation:" + pq.Location; jobList = jobList + "\n\t\tJob: " + job.JobName + " ID: " + job.JobIdentifier; } }// end for each print job }// end for each print queue
The next code example picks up the application at Step 2. (See above.) The problematic job has been identified and the application prompts for the information that will identify it. From this information it creates PrintServer, PrintQueue, and PrintSystemJobInfo objects.
At this point the application contains a branching structure corresponding to the two ways of checking a print job's status:
You can read the flags of the JobStatus property which is of type PrintJobStatus.
You can read each relevant property such as IsBlocked and IsInError.
This example demonstrates both methods, so the user was previously prompted as to which method to use and responded with "Y" if he or she wanted to use the flags of the JobStatus property. See below for the details of the two methods. Finally, the application uses a method called ReportQueueAndJobAvailability to report on whether the job can be printed at this time of day. This method is discussed in How to: Discover Whether a Print Job Can Be Printed At This Time of Day.
// When the problematic print job has been identified, enter information about it. Console.Write("\nEnter the print server hosting the job (including leading slashes \\\\): " + "\n(press Return for the current computer \\\\{0}): ", Environment.MachineName); String pServer = Console.ReadLine(); if (pServer == "") { pServer = "\\\\" +Environment.MachineName; } Console.Write("\nEnter the print queue hosting the job: "); String pQueue = Console.ReadLine(); Console.Write("\nEnter the job ID: "); Int16 jobID = Convert.ToInt16(Console.ReadLine()); // Create objects to represent the server, queue, and print job. PrintServer hostingServer = new PrintServer(pServer, PrintSystemDesiredAccess.AdministrateServer); PrintQueue hostingQueue = new PrintQueue(hostingServer, pQueue, PrintSystemDesiredAccess.AdministratePrinter); PrintSystemJobInfo theJob = hostingQueue.GetJob(jobID); if (useAttributesResponse == "Y") { TroubleSpotter.SpotTroubleUsingJobAttributes(theJob); // TroubleSpotter class is defined in the complete example. } else { TroubleSpotter.SpotTroubleUsingProperties(theJob); } TroubleSpotter.ReportQueueAndJobAvailability(theJob);
To check print job status using the flags of the JobStatus property, you check each relevant flag to see if it is set. The standard way to see if one bit is set in a set of bit flags is to perform a logical AND operation with the set of flags as one operand and the flag itself as the other. Since the flag itself has only one bit set, the result of the logical AND is that, at most, that same bit is set. To find out whether it is or not, just compare the result of the logical AND with the flag itself. For more information, see PrintJobStatus, the & Operator (C# Reference), and FlagsAttribute.
For each attribute whose bit is set, the code reports this to the console screen and sometimes suggests a way to respond. (The HandlePausedJobmethod that is called if the job or queue is paused is discussed below.)
// Check for possible trouble states of a print job using the flags of the JobStatus property internal static void SpotTroubleUsingJobAttributes(PrintSystemJobInfo theJob) { if ((theJob.JobStatus & PrintJobStatus.Blocked) == PrintJobStatus.Blocked) { Console.WriteLine("The job is blocked."); } if (((theJob.JobStatus & PrintJobStatus.Completed) == PrintJobStatus.Completed) || ((theJob.JobStatus & PrintJobStatus.Printed) == PrintJobStatus.Printed)) { Console.WriteLine("The job has finished. Have user recheck all output bins and be sure the correct printer is being checked."); } if (((theJob.JobStatus & PrintJobStatus.Deleted) == PrintJobStatus.Deleted) || ((theJob.JobStatus & PrintJobStatus.Deleting) == PrintJobStatus.Deleting)) { Console.WriteLine("The user or someone with administration rights to the queue has deleted the job. It must be resubmitted."); } if ((theJob.JobStatus & PrintJobStatus.Error) == PrintJobStatus.Error) { Console.WriteLine("The job has errored."); } if ((theJob.JobStatus & PrintJobStatus.Offline) == PrintJobStatus.Offline) { Console.WriteLine("The printer is offline. Have user put it online with printer front panel."); } if ((theJob.JobStatus & PrintJobStatus.PaperOut) == PrintJobStatus.PaperOut) { Console.WriteLine("The printer is out of paper of the size required by the job. Have user add paper."); } if (((theJob.JobStatus & PrintJobStatus.Paused) == PrintJobStatus.Paused) || ((theJob.HostingPrintQueue.QueueStatus & PrintQueueStatus.Paused) == PrintQueueStatus.Paused)) { HandlePausedJob(theJob); //HandlePausedJob is defined in the complete example. } if ((theJob.JobStatus & PrintJobStatus.Printing) == PrintJobStatus.Printing) { Console.WriteLine("The job is printing now."); } if ((theJob.JobStatus & PrintJobStatus.Spooling) == PrintJobStatus.Spooling) { Console.WriteLine("The job is spooling now."); } if ((theJob.JobStatus & PrintJobStatus.UserIntervention) == PrintJobStatus.UserIntervention) { Console.WriteLine("The printer needs human intervention."); } }//end SpotTroubleUsingJobAttributes
To check print job status using separate properties, you simply read each property and, if the property is true, report to the console screen and possibly suggest a way to respond. (The HandlePausedJob method that is called if the job or queue is paused is discussed below.)
// Check for possible trouble states of a print job using its properties internal static void SpotTroubleUsingProperties(PrintSystemJobInfo theJob) { if (theJob.IsBlocked) { Console.WriteLine("The job is blocked."); } if (theJob.IsCompleted || theJob.IsPrinted) { Console.WriteLine("The job has finished. Have user recheck all output bins and be sure the correct printer is being checked."); } if (theJob.IsDeleted || theJob.IsDeleting) { Console.WriteLine("The user or someone with administration rights to the queue has deleted the job. It must be resubmitted."); } if (theJob.IsInError) { Console.WriteLine("The job has errored."); } if (theJob.IsOffline) { Console.WriteLine("The printer is offline. Have user put it online with printer front panel."); } if (theJob.IsPaperOut) { Console.WriteLine("The printer is out of paper of the size required by the job. Have user add paper."); } if (theJob.IsPaused || theJob.HostingPrintQueue.IsPaused) { HandlePausedJob(theJob); //HandlePausedJob is defined in the complete example. } if (theJob.IsPrinting) { Console.WriteLine("The job is printing now."); } if (theJob.IsSpooling) { Console.WriteLine("The job is spooling now."); } if (theJob.IsUserInterventionRequired) { Console.WriteLine("The printer needs human intervention."); } }//end SpotTroubleUsingProperties
The HandlePausedJob method enables the application's user to remotely resume paused jobs. Because there might be a good reason why the print queue was paused, the method begins by prompting for a user decision about whether to resume it. If the answer is "Y", then the PrintQueue.Resumemethod is called.
Next the user is prompted to decide if the job itself should be resumed, just in case it is paused independently of the print queue. (Compare PrintQueue.IsPaused and PrintSystemJobInfo.IsPaused.) If the answer is "Y", then PrintSystemJobInfo.Resume is called; otherwise Cancel is called.
internal static void HandlePausedJob(PrintSystemJobInfo theJob) { // If there's no good reason for the queue to be paused, resume it and // give user choice to resume or cancel the job. Console.WriteLine("The user or someone with administrative rights to the queue" + "\nhas paused the job or queue." + "\nResume the queue? (Has no effect if queue is not paused.)" + "\nEnter \"Y\" to resume, otherwise press return: "); String resume = Console.ReadLine(); if (resume == "Y") { theJob.HostingPrintQueue.Resume(); // It is possible the job is also paused. Find out how the user wants to handle that. Console.WriteLine("Does user want to resume print job or cancel it?" + "\nEnter \"Y\" to resume (any other key cancels the print job): "); String userDecision = Console.ReadLine(); if (userDecision == "Y") { theJob.Resume(); } else { theJob.Cancel(); } }//end if the queue should be resumed }//end HandlePausedJob
How to: Discover Whether a Print Job Can Be Printed At This Time of Day
Print queues are not always available for 24 hours a day. They have start and end time properties that can be set to make them unavailable at certain times of day. This feature can be used, for example, to reserve a printer for the exclusive use of a certain department after 5 P.M.. That department would have a different queue servicing the printer than other departments use. The queue for the other departments would be set to be unavailable after 5 P.M., while queue for the favored department could be set to be available at all times.
Moreover, print jobs themselves can be set to be printable only within a specified span of time.
The PrintQueue and PrintSystemJobInfo classes exposed in the APIs of Microsoft .NET Framework provide a means for remotely checking whether a given print job can print on a given queue at the current time.
Example
The example below is a sample that can diagnose problems with a print job.
There are two major steps for this kind of function as follows.
Read the StartTimeOfDay and UntilTimeOfDay properties of the PrintQueue to determine whether the current time is between them.
Read the StartTimeOfDay and UntilTimeOfDay properties of the PrintSystemJobInfo to determine whether the current time is between them.
But complications arise from the fact that these properties are not DateTime objects. Instead they are Int32 objects that express the time of day as the number of minutes since midnight. Moreover, this is not midnight in the current time zone, but midnight UTC (Coordinated Universal Time).
The first code example presents the static method ReportQueueAndJobAvailability, which is passed a PrintSystemJobInfo and calls helper methods to determine whether the job can print at the current time and, if not, when it can print. Notice that a PrintQueue is not passed to the method. This is because the PrintSystemJobInfo includes a reference to the queue in its HostingPrintQueue property.
The subordinate methods include the overloaded ReportAvailabilityAtThisTime method which can take either a PrintQueue or a PrintSystemJobInfoas a parameter. There is also a TimeConverter.ConvertToLocalHumanReadableTime. All of these methods are discussed below.
The ReportQueueAndJobAvailability method begins by checking to see if either the queue or the print job is unavailable at this time. If either of them is unavailable, it then checks to see if the queue unavailable. If it is not available, then the method reports this fact and the time when the queue will become available again. It then checks the job and if it is unavailable, it reports the next time span when it when it can print. Finally, the method reports the earliest time when the job can print. This is the later of following two times.
The time when the print queue is next available.
The time when the print job is next available.
When reporting times of day, the ToShortTimeString method is also called because this method suppresses the years, months, and days from the output. You cannot restrict the availability of either a print queue or a print job to particular years, months, or days.
internal static void ReportQueueAndJobAvailability(PrintSystemJobInfo theJob) { if (!(ReportAvailabilityAtThisTime(theJob.HostingPrintQueue) && ReportAvailabilityAtThisTime(theJob))) { if (!ReportAvailabilityAtThisTime(theJob.HostingPrintQueue)) { Console.WriteLine("\nThat queue is not available at this time of day." + "\nJobs in the queue will start printing again at {0}", TimeConverter.ConvertToLocalHumanReadableTime(theJob.HostingPrintQueue.StartTimeOfDay).ToShortTimeString()); // TimeConverter class is defined in the complete sample } if (!ReportAvailabilityAtThisTime(theJob)) { Console.WriteLine("\nThat job is set to print only between {0} and {1}", TimeConverter.ConvertToLocalHumanReadableTime(theJob.StartTimeOfDay).ToShortTimeString(), TimeConverter.ConvertToLocalHumanReadableTime(theJob.UntilTimeOfDay).ToShortTimeString()); } Console.WriteLine("\nThe job will begin printing as soon as it reaches the top of the queue after:"); if (theJob.StartTimeOfDay > theJob.HostingPrintQueue.StartTimeOfDay) { Console.WriteLine(TimeConverter.ConvertToLocalHumanReadableTime(theJob.StartTimeOfDay).ToShortTimeString()); } else { Console.WriteLine(TimeConverter.ConvertToLocalHumanReadableTime(theJob.HostingPrintQueue.StartTimeOfDay).ToShortTimeString()); } }//end if at least one is not available }//end ReportQueueAndJobAvailability
The two overloads of the ReportAvailabilityAtThisTime method are identical except for the type passed to them, so only the PrintQueue version is presented below.
Note |
---|
The fact that the methods are identical except for type raises the question of why the sample does not create a generic method ReportAvailabilityAtThisTime<T>. The reason is that such a method would have to be restricted to a class that has the StartTimeOfDay and UntilTimeOfDay properties that the method calls, but a generic method can only be restricted to a single class and the only class common to both PrintQueue and PrintSystemJobInfo in the inheritance tree is PrintSystemObject which has no such properties. |
The ReportAvailabilityAtThisTime method (presented in the code example below) begins by initializing a Boolean sentinel variable to true. It will be reset to false, if the queue is not available.
Next, the method checks to see if the start and "until" times are identical. If they are, the queue is always available, so the method returns true.
If the queue is not available all the time, the method uses the static UtcNow property to get the current time as a DateTime object. (We do not need local time because the StartTimeOfDay and UntilTimeOfDay properties are themselves in UTC time.)
However, these two properties are not DateTime objects. They are Int32s expressing the time as the number of minutes-after-UTC-midnight. So we do have to convert our DateTime object to minutes-after-midnight. When that is done, the method simply checks to see whether "now" is between the queue's start and "until" times, sets the sentinel to false if "now" is not between the two times, and returns the sentinel.
private static Boolean ReportAvailabilityAtThisTime(PrintQueue pq) { Boolean available = true; if (pq.StartTimeOfDay != pq.UntilTimeOfDay) // If the printer is not available 24 hours a day { DateTime utcNow = DateTime.UtcNow; Int32 utcNowAsMinutesAfterMidnight = (utcNow.TimeOfDay.Hours * 60) + utcNow.TimeOfDay.Minutes; // If now is not within the range of available times . . . if (!((pq.StartTimeOfDay < utcNowAsMinutesAfterMidnight) && (utcNowAsMinutesAfterMidnight < pq.UntilTimeOfDay))) { available = false; } } return available; }//end ReportAvailabilityAtThisTime
The TimeConverter.ConvertToLocalHumanReadableTime method (presented in the code example below) does not use any methods introduced with Microsoft .NET Framework, so the discussion is brief. The method has a double conversion task: it must take an integer expressing minutes-after-midnight and convert it to a human-readable time and it must convert this to the local time. It accomplishes this by first creating a DateTime object that is set to midnight UTC and then it uses the AddMinutes method to add the minutes that were passed to the method. This returns a new DateTimeexpressing the original time that was passed to the method. The ToLocalTime method then converts this to local time.
class TimeConverter { // Convert time as minutes past UTC midnight into human readable time in local time zone. internal static DateTime ConvertToLocalHumanReadableTime(Int32 timeInMinutesAfterUTCMidnight) { // Construct a UTC midnight object. // Must start with current date so that the local Daylight Savings system, if any, will be taken into account. DateTime utcNow = DateTime.UtcNow; DateTime utcMidnight = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, 0, 0, 0, DateTimeKind.Utc); // Add the minutes passed into the method in order to get the intended UTC time. Double minutesAfterUTCMidnight = (Double)timeInMinutesAfterUTCMidnight; DateTime utcTime = utcMidnight.AddMinutes(minutesAfterUTCMidnight); // Convert to local time. DateTime localTime = utcTime.ToLocalTime(); return localTime; }// end ConvertToLocalHumanReadableTime }//end TimeConverter class
How to: Enumerate a Subset of Print Queues
A common situation faced by information technology (IT) professionals managing a company-wide set of printers is to generate a list of printers having certain characteristics. This functionality is provided by the GetPrintQueues method of a PrintServer object and the EnumeratedPrintQueueTypesenumeration.
Example
In the example below, the code begins by creating an array of flags that specify the characteristics of the print queues we want to list. In this example, we are looking for print queues that are installed locally on the print server and are shared. The EnumeratedPrintQueueTypes enumeration provides many other possibilities.
The code then creates a LocalPrintServer object, a class derived from PrintServer. The local print server is the computer on which the application is running.
The last significant step is to pass the array to the GetPrintQueues method.
Finally, the results are presented to the user.
// Specify that the list will contain only the print queues that are installed as local and are shared EnumeratedPrintQueueTypes[] enumerationFlags = {EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Shared}; LocalPrintServer printServer = new LocalPrintServer(); //Use the enumerationFlags to filter out unwanted print queues PrintQueueCollection printQueuesOnLocalServer = printServer.GetPrintQueues(enumerationFlags); Console.WriteLine("These are your shared, local print queues:\n\n"); foreach (PrintQueue printer in printQueuesOnLocalServer) { Console.WriteLine("\tThe shared printer " + printer.Name + " is located at " + printer.Location + "\n"); } Console.WriteLine("Press enter to continue."); Console.ReadLine();
You could extend this example by having the foreach loop that steps through each print queue do further screening. For example, you could screen out printers that do not support two-sided printing by having the loop call each print queue's GetPrintCapabilities method and test the returned value for the presence of duplexing.
How to: Get Print System Object Properties Without Reflection
Using reflection to itemize the properties (and the types of those properties) on an object can slow application performance. The System.Printing.IndexedProperties namespace provides a means to getting this information with using reflection.
Example
The steps for doing this are as follows.
Create an instance of the type. In the example below, the type is the PrintQueue type that ships with Microsoft .NET Framework, but nearly identical code should work for types that you derive from PrintSystemObject.
Create a PrintPropertyDictionary from the type's PropertiesCollection. The Value property of each entry in this dictionary is an object of one of the types derived from PrintProperty.
Enumerate the members of the dictionary. For each of them, do the following.
Up-cast the value of each entry to PrintProperty and use it to create a PrintProperty object.
Get the type of the Value of each of the PrintProperty object.
// Enumerate the properties, and their types, of a queue without using Reflection LocalPrintServer localPrintServer = new LocalPrintServer(); PrintQueue defaultPrintQueue = LocalPrintServer.GetDefaultPrintQueue(); PrintPropertyDictionary printQueueProperties = defaultPrintQueue.PropertiesCollection; Console.WriteLine("These are the properties, and their types, of {0}, a {1}", defaultPrintQueue.Name, defaultPrintQueue.GetType().ToString() +"\n"); foreach (DictionaryEntry entry in printQueueProperties) { PrintProperty property = (PrintProperty)entry.Value; if (property.Value != null) { Console.WriteLine(property.Name + "\t(Type: {0})", property.Value.GetType().ToString()); } } Console.WriteLine("\n\nPress Return to continue..."); Console.ReadLine();
How to: Programmatically Print XPS Files
You can use one overload of the AddJob method to print XML Paper Specification (XPS) files without opening a PrintDialog or, in principle, any user interface (UI) at all.
You can also print XML Paper Specification (XPS) files using the many Write and WriteAsync methods of the XpsDocumentWriter. For more about this, Printing an XPS Document.
Another way of printing XML Paper Specification (XPS) is to use thePrintDocument or PrintVisual methods of the PrintDialog control. See How to: Invoke a Print Dialog.
Example
The main steps to using the three-parameter AddJob(String, String, Boolean) method are as follows. The example below gives details.
Determine if the printer is an XPSDrv printer. (See Printing Overview for more about XPSDrv.)
If the printer is not an XPSDrv printer, set the thread's apartment to single thread.
Instantiate a print server and print queue object.
Call the method, specifying a job name, the file to be printed, and a Boolean flag indicating whether or not the printer is an XPSDrv printer.
The example below shows how to batch print all XPS files in a directory. Although the application prompts the user to specify the directory, the three-parameter AddJob(String, String, Boolean) method does not require a user interface (UI). It can be used in any code path where you have an XPS file name and path that you can pass to it.
The three-parameter AddJob(String, String, Boolean) overload of AddJob must run in a single thread apartment whenever the Boolean parameter is false, which it must be when a non-XPSDrv printer is being used. However, the default apartment state for Microsoft .NET is multiple thread. This default must be reversed since the example assumes a non-XPSDrv printer.
There are two ways to change the default. One way is to simply add the STAThreadAttribute (that is, "[System.STAThreadAttribute()]") just above the first line of the application's Main method (usually "static void Main(string[] args)"). However, many applications require that the Mainmethod have a multi-threaded apartment state, so there is a second method: put the call to AddJob(String, String, Boolean) in a separate thread whose apartment state is set to STA with SetApartmentState. The example below uses this second technique.
Accordingly, the example begins by instantiating a Thread object and passing it a PrintXPS method as the ThreadStart parameter. (The PrintXPSmethod is defined later in the example.) Next the thread is set to a single thread apartment. The only remaining code of the Main method starts the new thread.
The meat of the example is in the static BatchXPSPrinter.PrintXPS method. After creating a print server and queue, the method prompts the user for a directory containing XPS files. After validating the existence of the directory and the presence of *.xps files in it, the method adds each such file to the print queue. The example assumes that the printer is non-XPSDrv, so we are passing false to the last parameter of AddJob(String, String, Boolean)method. For this reason, the method will validate the XPS markup in the file before it attempts to convert it to the printer's page description language. If the validation fails, an exception is thrown. The example code will catch the exception, notify the user about it, and then go on to process the next XPS file.
class Program { [System.MTAThreadAttribute()] // Added for clarity, but this line is redundant because MTA is the default. static void Main(string[] args) { // Create the secondary thread and pass the printing method for // the constructor's ThreadStart delegate parameter. The BatchXPSPrinter // class is defined below. Thread printingThread = new Thread(BatchXPSPrinter.PrintXPS); // Set the thread that will use PrintQueue.AddJob to single threading. printingThread.SetApartmentState(ApartmentState.STA); // Start the printing thread. The method passed to the Thread // constructor will execute. printingThread.Start(); }//end Main }//end Program class public class BatchXPSPrinter { public static void PrintXPS() { // Create print server and print queue. LocalPrintServer localPrintServer = new LocalPrintServer(); PrintQueue defaultPrintQueue = LocalPrintServer.GetDefaultPrintQueue(); // Prompt user to identify the directory, and then create the directory object. Console.Write("Enter the directory containing the XPS files: "); String directoryPath = Console.ReadLine(); DirectoryInfo dir = new DirectoryInfo(directoryPath); // If the user mistyped, end the thread and return to the Main thread. if (!dir.Exists) { Console.WriteLine("There is no such directory."); } else { // If there are no XPS files in the directory, end the thread // and return to the Main thread. if (dir.GetFiles("*.xps").Length == 0) { Console.WriteLine("There are no XPS files in the directory."); } else { Console.WriteLine("\nJobs will now be added to the print queue."); Console.WriteLine("If the queue is not paused and the printer is working, jobs will begin printing."); // Batch process all XPS files in the directory. foreach (FileInfo f in dir.GetFiles("*.xps")) { String nextFile = directoryPath + "\\" + f.Name; Console.WriteLine("Adding {0} to queue.", nextFile); try { // Print the Xps file while providing XPS validation and progress notifications. PrintSystemJobInfo xpsPrintJob = defaultPrintQueue.AddJob(f.Name, nextFile, false); } catch (PrintJobException e) { Console.WriteLine("\n\t{0} could not be added to the print queue.", f.Name); if (e.InnerException.Message == "File contains corrupted data.") { Console.WriteLine("\tIt is not a valid XPS file. Use the isXPS Conformance Tool to debug it."); } Console.WriteLine("\tContinuing with next XPS file.\n"); } }// end for each XPS file }//end if there are no XPS files in the directory }//end if the directory does not exist Console.WriteLine("Press Enter to end program."); Console.ReadLine(); }// end PrintXPS method }// end BatchXPSPrinter class
If you are using an XPSDrv printer, then you can set the final parameter to true. In that case, since XPS is the printer's page description language, the method will send the file to the printer without validating it or converting it to another page description language. If you are uncertain at design time whether the application will be using an XPSDrv printer, you can modify the application to have it read the IsXpsDevice property and branch according to what it finds.
Since there will initially be few XPSDrv printers available immediately after the release of Windows Vista and Microsoft .NET Framework, you may need to disguise a non-XPSDrv printer as an XPSDrv printer. To do so, add Pipelineconfig.xml to the list of files in the following registry key of the computer running your application:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows NT x86\Drivers\Version-3\<PseudoXPSPrinter>\DependentFiles
where <PseudoXPSPrinter> is any print queue. The machine must then be rebooted.
This disguise will enable you to pass true as the final parameter of AddJob(String, String, Boolean) without causing an exception, but since <PseudoXPSPrinter> is not really an XPSDrv printer, only garbage will print.
Note For simplicity, the example above uses the presence of an *.xps extension as its test that a file is XPS. However, XPS files do not have to have this extension. The isXPS.exe (isXPS Conformance Tool) is one way of testing a file for XPS validity.
How to: Remotely Survey the Status of Printers
At any given time at medium and large companies there may be multiple printers that are not working due to a paper jam or being out of paper or some other problematic situation. The rich set of printer properties exposed in the APIs of Microsoft .NET Framework provide a means for performing a rapid survey of the states of printers.
Example
The major steps for creating this kind of utility are as follows.
Obtain a list of all print servers.
Loop through the servers to query their print queues.
Within each pass of the server loop, loop through all the server's queues and read each property that might indicate that the queue is not currently working.
The code below is a series of snippets. For simplicity, this example assumes that there is a CRLF-delimited list of print servers. The variable fileOfPrintServers is a StreamReader object for this file. Since each server name is on its own line, any call of ReadLine gets the name of the next server and moves the StreamReader's cursor to the beginning of the next line.
Within the outer loop, the code creates a PrintServer object for the latest print server and specifies that the application is to have administrative rights to the server.
Note |
---|
If there are a lot of servers, you can improve performance by using the PrintServer(String, String[], PrintSystemDesiredAccess) constructors that only initialize the properties you are going to need. |
The example then uses GetPrintQueues to create a collection of all of the server's queues and begins to loop through them. This inner loop contains a branching structure corresponding to the two ways of checking a printer's status:
You can read the flags of the QueueStatus property which is of type PrintQueueStatus.
You can read each relevant property such as IsOutOfPaper, and IsPaperJammed.
This example demonstrates both methods, so the user was previously prompted as to which method to use and responded with "y" if he or she wanted to use the flags of the QueueStatus property. See below for the details of the two methods.
Finally, the results are presented to the user.
// Survey queue status for every queue on every print server String line; String statusReport = "\n\nAny problem states are indicated below:\n\n"; while ((line = fileOfPrintServers.ReadLine()) != null) { PrintServer myPS = new PrintServer(line, PrintSystemDesiredAccess.AdministrateServer); PrintQueueCollection myPrintQueues = myPS.GetPrintQueues(); statusReport = statusReport + "\n" + line; foreach (PrintQueue pq in myPrintQueues) { pq.Refresh(); statusReport = statusReport + "\n\t" + pq.Name + ":"; if (useAttributesResponse == "y") { TroubleSpotter.SpotTroubleUsingQueueAttributes(ref statusReport, pq); // TroubleSpotter class is defined in the complete example. } else { TroubleSpotter.SpotTroubleUsingProperties(ref statusReport, pq); } }// end for each print queue }// end while list of print servers is not yet exhausted fileOfPrintServers.Close(); Console.WriteLine(statusReport); Console.WriteLine("\nPress Return to continue."); Console.ReadLine();
To check printer status using the flags of the QueueStatus property, you check each relevant flag to see if it is set. The standard way to see if one bit is set in a set of bit flags is to perform a logical AND operation with the set of flags as one operand and the flag itself as the other. Since the flag itself has only one bit set, the result of the logical AND is that, at most, that same bit is set. To find out whether it is or not, just compare the result of the logical AND with the flag itself. For more information, see PrintQueueStatus, the & Operator (C# Reference), and FlagsAttribute.
For each attribute whose bit is set, the code adds a notice to the final report that will be presented to the user. (The ReportAvailabilityAtThisTimemethod that is called at the end of the code is discussed below.)
// Check for possible trouble states of a printer using the flags of the QueueStatus property internal static void SpotTroubleUsingQueueAttributes(ref String statusReport, PrintQueue pq) { if ((pq.QueueStatus & PrintQueueStatus.PaperProblem) == PrintQueueStatus.PaperProblem) { statusReport = statusReport + "Has a paper problem. "; } if ((pq.QueueStatus & PrintQueueStatus.NoToner) == PrintQueueStatus.NoToner) { statusReport = statusReport + "Is out of toner. "; } if ((pq.QueueStatus & PrintQueueStatus.DoorOpen) == PrintQueueStatus.DoorOpen) { statusReport = statusReport + "Has an open door. "; } if ((pq.QueueStatus & PrintQueueStatus.Error) == PrintQueueStatus.Error) { statusReport = statusReport + "Is in an error state. "; } if ((pq.QueueStatus & PrintQueueStatus.NotAvailable) == PrintQueueStatus.NotAvailable) { statusReport = statusReport + "Is not available. "; } if ((pq.QueueStatus & PrintQueueStatus.Offline) == PrintQueueStatus.Offline) { statusReport = statusReport + "Is off line. "; } if ((pq.QueueStatus & PrintQueueStatus.OutOfMemory) == PrintQueueStatus.OutOfMemory) { statusReport = statusReport + "Is out of memory. "; } if ((pq.QueueStatus & PrintQueueStatus.PaperOut) == PrintQueueStatus.PaperOut) { statusReport = statusReport + "Is out of paper. "; } if ((pq.QueueStatus & PrintQueueStatus.OutputBinFull) == PrintQueueStatus.OutputBinFull) { statusReport = statusReport + "Has a full output bin. "; } if ((pq.QueueStatus & PrintQueueStatus.PaperJam) == PrintQueueStatus.PaperJam) { statusReport = statusReport + "Has a paper jam. "; } if ((pq.QueueStatus & PrintQueueStatus.Paused) == PrintQueueStatus.Paused) { statusReport = statusReport + "Is paused. "; } if ((pq.QueueStatus & PrintQueueStatus.TonerLow) == PrintQueueStatus.TonerLow) { statusReport = statusReport + "Is low on toner. "; } if ((pq.QueueStatus & PrintQueueStatus.UserIntervention) == PrintQueueStatus.UserIntervention) { statusReport = statusReport + "Needs user intervention. "; } // Check if queue is even available at this time of day // The method below is defined in the complete example. ReportAvailabilityAtThisTime(ref statusReport, pq); }
To check printer status using each property, you simply read each property and add a note to the final report that will be presented to the user if the property is true. (The ReportAvailabilityAtThisTime method that is called at the end of the code is discussed below.)
// Check for possible trouble states of a printer using its properties internal static void SpotTroubleUsingProperties(ref String statusReport, PrintQueue pq) { if (pq.HasPaperProblem) { statusReport = statusReport + "Has a paper problem. "; } if (!(pq.HasToner)) { statusReport = statusReport + "Is out of toner. "; } if (pq.IsDoorOpened) { statusReport = statusReport + "Has an open door. "; } if (pq.IsInError) { statusReport = statusReport + "Is in an error state. "; } if (pq.IsNotAvailable) { statusReport = statusReport + "Is not available. "; } if (pq.IsOffline) { statusReport = statusReport + "Is off line. "; } if (pq.IsOutOfMemory) { statusReport = statusReport + "Is out of memory. "; } if (pq.IsOutOfPaper) { statusReport = statusReport + "Is out of paper. "; } if (pq.IsOutputBinFull) { statusReport = statusReport + "Has a full output bin. "; } if (pq.IsPaperJammed) { statusReport = statusReport + "Has a paper jam. "; } if (pq.IsPaused) { statusReport = statusReport + "Is paused. "; } if (pq.IsTonerLow) { statusReport = statusReport + "Is low on toner. "; } if (pq.NeedUserIntervention) { statusReport = statusReport + "Needs user intervention. "; } // Check if queue is even available at this time of day // The following method is defined in the complete example. ReportAvailabilityAtThisTime(ref statusReport, pq); }//end SpotTroubleUsingProperties
The ReportAvailabilityAtThisTime method was created in case you need to determine if the queue is available at the current time of day.
The method will do nothing if the StartTimeOfDay and UntilTimeOfDay properties are equal; because in that case the printer is available at all times. If they are different, the method gets the current time which then has to be converted into total minutes past midnight because the StartTimeOfDay and UntilTimeOfDay properties are Int32s representing minutes-after-midnight, not DateTime objects. Finally, the method checks to see if the current time is between the start and "until" times.
private static void ReportAvailabilityAtThisTime(ref String statusReport, PrintQueue pq) { if (pq.StartTimeOfDay != pq.UntilTimeOfDay) // If the printer is not available 24 hours a day { DateTime utcNow = DateTime.UtcNow; Int32 utcNowAsMinutesAfterMidnight = (utcNow.TimeOfDay.Hours * 60) + utcNow.TimeOfDay.Minutes; // If now is not within the range of available times . . . if (!((pq.StartTimeOfDay < utcNowAsMinutesAfterMidnight) && (utcNowAsMinutesAfterMidnight < pq.UntilTimeOfDay))) { statusReport = statusReport + " Is not available at this time of day. "; } } }
How to: Validate and Merge PrintTickets
The Microsoft Windows Print Schema includes the flexible and extensible PrintCapabilities and PrintTicket elements. The former itemizes the capabilities of a print device and the latter specifies how the device should use those capabilities with respect to a particular sequence of documents, individual document, or individual page.
A typical sequence of tasks for an application that supports printing would be as follows.
Determine a printer's capabilities.
Configure a PrintTicket to use those capabilities.
Validate the PrintTicket.
This article shows how to do this.
Example
In the simple example below, we are interested only in whether a printer can support duplexing — two-sided printing. The major steps are as follows.
Get a PrintCapabilities object with the GetPrintCapabilities method.
Test for the presence of the capability you want. In the example below, we test the DuplexingCapability property of the PrintCapabilities object for the presence of the capability of printing on both sides of a sheet of paper with the "page turning" along the long side of the sheet. Since DuplexingCapability is a collection, we use the Contains method of ReadOnlyCollection<T>.
Note This step is not strictly necessary. The MergeAndValidatePrintTicket method used below will check each request in the PrintTicket against the capabilities of the printer. If the requested capability is not supported by printer, the printer driver will substitute an alternative request in the PrintTicket returned by the method.
If the printer supports duplexing, the sample code creates a PrintTicket that asks for duplexing. But the application does not specify every possible printer setting available in the PrintTicket element. That would be wasteful of both programmer and program time. Instead, the code sets only the duplexing request and then merges this PrintTicket with an existing, fully configured and validated, PrintTicket, in this case, the user's default PrintTicket.
Accordingly, the sample calls the MergeAndValidatePrintTicket method to merge the new, minimal, PrintTicket with the user's default PrintTicket. This returns a ValidationResult that includes the new PrintTicket as one of its properties.
The sample then tests that the new PrintTicket requests duplexing. If it does, then the sample makes it the new default print ticket for the user. If step 2 above had been left out and the printer did not support duplexing along the long side, then the test would have resulted in false. (See the note above.)
The last significant step is to commit the change to the UserPrintTicket property of the PrintQueue with the Commit method.
/// <summary> /// Changes the user-default PrintTicket setting of the specified print queue. /// </summary> /// <param name="queue">the printer whose user-default PrintTicket setting needs to be changed</param> static private void ChangePrintTicketSetting(PrintQueue queue) { // // Obtain the printer's PrintCapabilities so we can determine whether or not // duplexing printing is supported by the printer. // PrintCapabilities printcap = queue.GetPrintCapabilities(); // // The printer's duplexing capability is returned as a read-only collection of duplexing options // that can be supported by the printer. If the collection returned contains the duplexing // option we want to set, it means the duplexing option we want to set is supported by the printer, // so we can make the user-default PrintTicket setting change. // if (printcap.DuplexingCapability.Contains(Duplexing.TwoSidedLongEdge)) { // // To change the user-default PrintTicket, we can first create a delta PrintTicket with // the new duplexing setting. // PrintTicket deltaTicket = new PrintTicket(); deltaTicket.Duplexing = Duplexing.TwoSidedLongEdge; // // Then merge the delta PrintTicket onto the printer's current user-default PrintTicket, // and validate the merged PrintTicket to get the new PrintTicket we want to set as the // printer's new user-default PrintTicket. // ValidationResult result = queue.MergeAndValidatePrintTicket(queue.UserPrintTicket, deltaTicket); // // The duplexing option we want to set could be constrained by other PrintTicket settings // or device settings. We can check the validated merged PrintTicket to see whether the // the validation process has kept the duplexing option we want to set unchanged. // if (result.ValidatedPrintTicket.Duplexing == Duplexing.TwoSidedLongEdge) { // // Set the printer's user-default PrintTicket and commit the set operation. // queue.UserPrintTicket = result.ValidatedPrintTicket; queue.Commit(); Console.WriteLine("PrintTicket new duplexing setting is set on '{0}'.", queue.FullName); } else { // // The duplexing option we want to set has been changed by the validation process // when it was resolving setting constraints. // Console.WriteLine("PrintTicket new duplexing setting is constrained on '{0}'.", queue.FullName); } } else { // // If the printer doesn't support the duplexing option we want to set, skip it. // Console.WriteLine("PrintTicket new duplexing setting is not supported on '{0}'.", queue.FullName); } }
So that you can quickly test this example, the remainder of it is presented below. Create a project and a namespace and then paste both the code snippets in this article into the namespace block.
/// <summary> /// Displays the correct command line syntax to run this sample program. /// </summary> static private void DisplayUsage() { Console.WriteLine(); Console.WriteLine("Usage #1: printticket.exe -l \"<printer_name>\""); Console.WriteLine(" Run program on the specified local printer"); Console.WriteLine(); Console.WriteLine(" Quotation marks may be omitted if there are no spaces in printer_name."); Console.WriteLine(); Console.WriteLine("Usage #2: printticket.exe -r \"\\\\<server_name>\\<printer_name>\""); Console.WriteLine(" Run program on the specified network printer"); Console.WriteLine(); Console.WriteLine(" Quotation marks may be omitted if there are no spaces in server_name or printer_name."); Console.WriteLine(); Console.WriteLine("Usage #3: printticket.exe -a"); Console.WriteLine(" Run program on all installed printers"); Console.WriteLine(); } [STAThread] static public void Main(string[] args) { try { if ((args.Length == 1) && (args[0] == "-a")) { // // Change PrintTicket setting for all local and network printer connections. // LocalPrintServer server = new LocalPrintServer(); EnumeratedPrintQueueTypes[] queue_types = {EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections}; // // Enumerate through all the printers. // foreach (PrintQueue queue in server.GetPrintQueues(queue_types)) { // // Change the PrintTicket setting queue by queue. // ChangePrintTicketSetting(queue); } }//end if -a else if ((args.Length == 2) && (args[0] == "-l")) { // // Change PrintTicket setting only for the specified local printer. // LocalPrintServer server = new LocalPrintServer(); PrintQueue queue = new PrintQueue(server, args[1]); ChangePrintTicketSetting(queue); }//end if -l else if ((args.Length == 2) && (args[0] == "-r")) { // // Change PrintTicket setting only for the specified remote printer. // String serverName = args[1].Remove(args[1].LastIndexOf(@"\")); String printerName = args[1].Remove(0, args[1].LastIndexOf(@"\")+1); PrintServer ps = new PrintServer(serverName); PrintQueue queue = new PrintQueue(ps, printerName); ChangePrintTicketSetting(queue); }//end if -r else { // // Unrecognized command line. // Show user the correct command line syntax to run this sample program. // DisplayUsage(); } } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); // // Show inner exception information if it's provided. // if (e.InnerException != null) { Console.WriteLine("--- Inner Exception ---"); Console.WriteLine(e.InnerException.Message); Console.WriteLine(e.InnerException.StackTrace); } } finally { Console.WriteLine("Press Return to continue..."); Console.ReadLine(); } }//end Main
'프로그래밍 > WPF' 카테고리의 다른 글
Layout (0) | 2016.11.05 |
---|---|
Globalization and Localization (0) | 2016.11.05 |
Resources (0) | 2016.11.01 |
Events (0) | 2016.11.01 |
Element Tree and Serialization (0) | 2016.11.01 |
The following figure shows how the table defined in this example renders.