달력

5

« 2025/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2016. 11. 5. 13:45

Documents 프로그래밍/WPF2016. 11. 5. 13:45



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: FlowDocumentReaderFlowDocumentPageViewer, 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: FlowDocumentReaderFlowDocumentPageViewer, 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:

PackagePart

Application content, data, documents, and resource files.

PackageDigitalSignature

X.509 Certificate for identification, authentication and validation.

PackageRelationship

Added information related to the package or a specific part.

PackageParts

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:

  1. Identifies and authenticates the originator of the part.

  2. 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

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:

  1. Defining dependency relationships from one part to another part.

  2. Defining information relationships that add notes or other data related to the part.

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:

  1. 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.

  2. 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.

  3. 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.

Highlight, text 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.

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 &amp; 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.

Annotation Data Anchoring

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 AuthorsContentLocatorPart, 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 AuthorsContentLocatorPart, 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 – AuthorsContentLocatorPart, 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.

Flow Document Content Reflow

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.

Screenshot: Rendered FlowDocument example

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. ParagraphListListItem, 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: FlowDocumentReaderFlowDocumentPageViewerRichTextBox, 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:

Diagram: Flow content element class hierarchy

For the purposes of flow content, there are two important categories:

  1. 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.

  2. 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.

Screenshot: UIElement embedded in flow content

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 HyperlinkBoldItalic 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.

Screenshot: Rendered Span example

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.

Screenshot: Figure example

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.

Screenshot: LineBreak example

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.

spanx.Inlines.Remove(spanx.Inlines.LastInline);

The following example clears all of the contents ( Inline elements) from the Span.

spanx.Inlines.Clear();

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.

Diagram: Flow content containment schema

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.

Diagram: RichTextBox containment rules

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 ParagraphSectionTableList, 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.

Diagram: Parent/child schema for Table

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.

Diagram: Parent/child schema for Paragraph
Diagram: Parent/Child schema for Run

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 InlineParagraphTextBlock, 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.

Screenshot: Text with default strikethrough effect

The following figures show how the OverlineBaseline, and Underline decorations render, respectively.

Screenshot: Overline TextDecorator
Screenshot: Default baseline effect on text
Screenshot: Text with default underline effect

Typography

The Typography property is exposed by most flow-related content including TextElementFlowDocumentTextBlock, 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.

Screenshot: Text with altered typography

In contrast, the following figure shows how a similar example with default typographic properties renders.

Screenshot: Text with altered typography

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.

Diagram: Flow content containment schema

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.

  1. RichTextBox must contain a FlowDocument which in turn must contain a Block-derived object. The following is the corresponding segment from the preceding diagram.

    Diagram: RichTextBox containment rules

    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 ParagraphSectionTableList, 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.

    Diagram: Parent/child schema for Table

    The following 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. 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.

    Diagram: Parent/child schema for Paragraph

    Diagram: Parent/Child schema for Run

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:

You can manipulate (add or remove items) from these collections using the respective properties of InlinesBlocks, and ListItems. The following examples show how to manipulate the contents of a Span using the Inlines property.

System_CAPS_noteNote

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.

spanx.Inlines.Remove(spanx.Inlines.LastInline);

The following example clears all of the contents ( Inline elements) from the Span.

spanx.Inlines.Clear();

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.

BoldFigureFloaterHyperlinkInlineUIContainerItalicLineBreakListListItemParagraphRunSectionSpanTableUnderline.

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 FlowDocumentTable 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.

System_CAPS_noteNote

TableCell elements may not directly host text content. For more information about the containment rules for flow content elements likeTableCell, see Flow Document Overview.

System_CAPS_noteNote

Table is similar to the Grid element but has more capabilities and, therefore, requires greater resource overhead.

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.

Screenshot: Render a basic table

Table Containment

Table derives from the Block element, and adheres to the common rules for Block level elements. A Table element may be contained by any of the following elements:

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.

Screenshot: Table row groups

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.

  1. Table

  2. TableColumn

  3. TableRowGroup

  4. TableRow

  5. TableCell

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).

Screenshot: Table z-order

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.

Screenshot: Cell spanning all three columns

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.

System_CAPS_noteNote

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.

System_CAPS_noteNote

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>

The following figure shows how the table defined in this example renders.

Rendered table.




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.

Screenshot: Text with altered typography

In contrast, the following figure shows how a similar example with default typographic properties renders.

Screenshot: Text with altered typography

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.

myTextBlock.TextTrimming = TextTrimming.CharacterEllipsis;

There are currently three options for trimming text: CharacterEllipsisWordEllipsis, 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.

Example: TextTrimming.CharacterEllipsis

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.

Example: TextTrimming.WordEllipsis

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.

Example: TextTrimming.None




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.

A Span element applied to a range of text




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.

Section secx = new Section();
secx.Blocks.Add(new Paragraph(new Run("A bit of text content...")));

Example

The following example creates a new Paragraph element and inserts it at the beginning of the Section.

Paragraph parx = new Paragraph(new Run("Text to insert..."));
secx.Blocks.InsertBefore(secx.Blocks.FirstBlock, parx);

Example

The following example gets the number of top-level Block elements contained in the Section.

int countTopLevelBlocks = secx.Blocks.Count;

Example

The following example deletes the last Block element in the Section.

secx.Blocks.Remove(secx.Blocks.LastBlock);

Example

The following example clears all of the contents (Block elements) from the Section.

secx.Blocks.Clear();




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

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..."));

Example

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);

Example

The following example gets the number of top-level Inline elements contained in the Span.

int countTopLevelInlines = spanx.Inlines.Count;

Example

The following example deletes the last Inline element in the Span.

spanx.Inlines.Remove(spanx.Inlines.LastInline);

Example

The following example clears all of the contents (Inline elements) from the Span.

spanx.Inlines.Clear();




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.

FlowDocument flowDoc = new FlowDocument(new Paragraph(new Run("A bit of text content...")));
flowDoc.Blocks.Add(new Paragraph(new Run("Text to append...")));

Example

The following example creates a new Paragraph element and inserts it at the beginning of the FlowDocument.

Paragraph p = new Paragraph(new Run("Text to insert..."));
flowDoc.Blocks.InsertBefore(flowDoc.Blocks.FirstBlock, p);

Example

The following example gets the number of top-level Block elements contained in the FlowDocument.

int countTopLevelBlocks = flowDoc.Blocks.Count;

Example

The following example deletes the last Block element in the FlowDocument.

flowDoc.Blocks.Remove(flowDoc.Blocks.LastBlock);

Example

The following example clears all of the contents (Block elements) from the FlowDocument.

flowDoc.Blocks.Clear();




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

The following example creates a new table and then uses the Add method to add columns to the table's Columns collection.

Table tbl = new Table();
int columnsToAdd = 4;
for (int x = 0; x < columnsToAdd; x++)
    tbl.Columns.Add(new TableColumn());

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.

System_CAPS_noteNote

The TableColumnCollection collection uses standard zero-based indexing.

tbl.Columns.Insert(0, new TableColumn());

Example

The following example accesses some arbitrary properties on columns in the TableColumnCollection collection, referring to particular columns by index.

tbl.Columns[0].Width = new GridLength(20);
tbl.Columns[1].Background = Brushes.AliceBlue;
tbl.Columns[2].Width = new GridLength(20);
tbl.Columns[3].Background = Brushes.AliceBlue;

Example

The following example gets the number of columns currently hosted by the table.

int columns = tbl.Columns.Count;

Example

The following example removes a particular column by reference.

tbl.Columns.Remove(tbl.Columns[3]);

Example

The following example removes a particular column by index.

tbl.Columns.RemoveAt(2);

Example

The following example removes all columns from the table's columns collection.

tbl.Columns.Clear();




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

The following example creates a new table and then uses the Add method to add columns to the table's RowGroups collection.

Table tbl = new Table();
int rowGroupsToAdd = 4;
for (int x = 0; x < rowGroupsToAdd; x++)
    tbl.RowGroups.Add(new TableRowGroup());

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.

System_CAPS_noteNote

The TableRowGroupCollection collection uses standard zero-based indexing.

tbl.RowGroups.Insert(0, new TableRowGroup());

Example

The following example adds several rows to a particular TableRowGroup (specified by index) in the table.

int rowsToAdd = 10;
for (int x = 0; x < rowsToAdd; x++)
    tbl.RowGroups[0].Rows.Add(new TableRow());

Example

The following example accesses some arbitrary properties on rows in the first row group in the table.

// Alias the working TableRowGroup for ease in referencing.
TableRowGroup trg = tbl.RowGroups[0];
trg.Rows[0].Background = Brushes.CornflowerBlue;
trg.Rows[1].FontSize = 24;
trg.Rows[2].ToolTip = "This row's tooltip";

Example

The following example adds several cells to a particular TableRow (specified by index) in the table.

int cellsToAdd = 10;
for (int x = 0; x < cellsToAdd; x++)
    tbl.RowGroups[0].Rows[0].Cells.Add(new TableCell(new Paragraph(new Run("Cell " + (x + 1)))));

Example

The following example access some arbitrary methods and properties on cells in the first row in the first row group.

// Alias the working for for ease in referencing.
TableRow row = tbl.RowGroups[0].Rows[0];
row.Cells[0].Background = Brushes.PapayaWhip;
row.Cells[1].FontStyle = FontStyles.Italic;
// This call clears all of the content from this cell.
row.Cells[2].Blocks.Clear();

Example

The following example returns the number of TableRowGroup elements hosted by the table.

int rowGroups = tbl.RowGroups.Count;

Example

The following example removes a particular row group by reference.

tbl.RowGroups.Remove(tbl.RowGroups[0]);

Example

The following example removes a particular row group by index.

tbl.RowGroups.RemoveAt(0);

Example

The following example removes all row groups from the table's row groups collection.

tbl.RowGroups.Clear();




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 ColumnGapColumnRuleBrush, 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 ColumnGapColumnRuleBrush, and ColumnRuleWidth attributes in a rendered FlowDocument.

Screenshot: FlowDocument Intra Column




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.

Text with ClearType y-direction anti-aliasing

Text with ClearType y-direction antialiasing

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.

Diagram of the text rendering pipeline

Diagram of the text rendering pipeline

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.

Text using OpenType stylistic alternate glyphs

Text using OpenType 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.

Text using OpenType standard and swash glyphs

Text using OpenType standard and swash glyphs

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 FontWeightFontStretch, 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 LabelTextBlock, 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 TextRangeTextSelection, and TextPointer enable useful text manipulation. These UI controls provide properties such as FontFamilyFontSize, 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.

Text shadow with Softness = 0.25

Text with a drop shadow

The following example shows a drop shadow effect and noise applied to text.

Text shadow with noise

Text with a drop shadow and noise

The following example shows an outer glow effect applied to text.

Text shadow using an OuterGlowBitmapEffect

Text with an outer glow effect

The following example shows a blur effect applied to text.

Text shadow using a BlurBitmapEffect

Text with a blur effect

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.

Text scaled using a ScaleTransform

Text using a ScaleTransform

The following example shows text skewed along the x-axis.

Text skewed using a SkewTransform

Text using a SkewTransform

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.

Screenshot of text effect rotating text

Example of a rotating text effect animation

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.

Using OpenType fonts sample screen shot

Text hosted in a FlowDocumentReader

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.

Text displayed using FormattedText object

Displayed text using FormattedText object

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.

Text outline using a linear gradient brush

Text outline using a linear gradient brush

The following examples illustrate several ways of creating interesting visual effects by modifying the stroke, fill, and highlight of converted text.

Text with different colors for fill and stroke

Example of setting stroke and fill to different colors

Text with image brush applied to stroke

Example of an image brush applied to the stroke

Text with image brush applied to stroke

Example of an image brush applied to the stroke and highlight

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.

Diagram of text layout client and TextFormatter

Interaction between 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.

Text displayed with two versions of ClearType

Text displayed with earlier and later versions of 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.

Text displayed with earlier version of ClearType

Text with earlier and later versions of ClearType

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.

Text with jagged edges on shallow curves

Text with jagged edges on shallow curves

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.

Text with ClearType y-direction anti-aliasing

Text with ClearType y-direction antialiasing

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 Tuner PowerToy




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. 

System_CAPS_noteNote

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.

ClearType settings in the Registry Editor
System_CAPS_noteNote

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.

ClearType settings in the Registry Editor

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.

System_CAPS_noteNote

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.

ClearType settings in the Registry Editor

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.

ClearType settings in the Registry Editor





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.

Text displayed using FormattedText object

Displayed text using FormattedText method

System_CAPS_noteNote

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.

Text displayed using FormattedText object

Displayed text showing wordwrapping and ellipsis

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.

// The font size is calculated in terms of points -- not as device-independent pixels.
formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5);

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.

Text outline using a linear gradient brush

Text outline using a linear gradient brush

The following examples illustrate several ways of creating interesting visual effects by modifying the stroke, fill, and highlight of converted text.

Text with different colors for fill and stroke

Example of setting stroke and fill to different colors

Text with image brush applied to stroke

Example of an image brush applied to the stroke

Text with image brush applied to stroke

Example of an image brush applied to the stroke and highlight

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.

Sphere following the path geometry of text

Sphere following the path geometry of 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.

Video displaying in the path geometry of text

Video displaying in the path geometry of text

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

Height

Use the Height property to compute an appropriate Win32 DrawText 'y' position.

DT_CALCRECT

HeightWidth

Use the Height and Width properties to calculate the output rectangle.

DT_CENTER

TextAlignment

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

Trimming

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

TextAlignment

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

VisualClip

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

TextAlignment

Use the TextAlignment property with the value set to Right. (WPF only)

DT_RTLREADING

FlowDirection

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

Height

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

Height

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

Trimming

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.

System_CAPS_noteNote

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.

Diagram of text layout client and TextFormatter

Interaction between 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 TextSourceTextSource 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:

  • 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

TextCharacters

The specialized text run used to pass a representation of character glyphs back to the text formatter.

TextEmbeddedObject

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.

TextEndOfLine

The specialized text run used to mark the end of a line.

TextEndOfParagraph

The specialized text run used to mark the end of a paragraph.

TextEndOfSegment

The specialized text run used to mark the end of a segment, such as to end the scope affected by a previous TextModifier run.

TextHidden

The specialized text run used to mark a range of hidden characters.

TextModifier

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);
}
System_CAPS_noteNote

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.

System_CAPS_noteNote

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.

Text using OpenType superscripts

Text using OpenType superscripts

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.

Text using OpenType subscripts

Text using OpenType subscripts

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.

Text using OpenType superscripts and subscripts

Text using OpenType superscripts and subscripts

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.

Text using OpenType capitals

Text using OpenType capitals

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.

Text using OpenType titling capitals

Text using OpenType titling capitals

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.

Text using OpenType capital spacing

Text using OpenType capital spacing

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.

Text using OpenType standard ligatures

Text using OpenType standard ligatures

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.

Text using OpenType discretionary ligatures

Text using OpenType discretionary ligatures

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.

Text using OpenType standard ligatures

Text using OpenType standard ligatures

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.

Text using disabled OpenType standard ligatures

Text using disabled OpenType standard ligatures

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.

Text using OpenType standard and swash glyphs

Text using OpenType standard and swash glyphs

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.

Text using OpenType swashes

Text using OpenType swashes

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.

Text using OpenType contextual swashes

Text using OpenType contextual swashes

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.

Text using OpenType standard glyphs

Text using OpenType standard glyphs

The Pericles OpenType font contains additional glyphs that provide stylistic alternates to the standard set of glyphs. The following text displays stylistic alternate glyphs.

Text using OpenType stylistic alternate glyphs

Text using OpenType 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.

Text using OpenType stylistic alternate glyphs

Text using OpenType stylistic alternate glyphs

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

Text using OpenType random contextual alternates

Text using OpenType random contextual alternates

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.

Text using OpenType historical forms

Text using OpenType historical forms

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.

Text using OpenType slashed and stacked fractions

Text using OpenType slashed and stacked fractions

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.

Text using OpenType old style numerals

Text using OpenType old style numerals

The following text displays standard numerals for the Palatino Linotype font, followed by old style numerals.

Text using OpenType old style numeral sets

Text using OpenType old style numeral sets

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.

Text using OpenType proportional & tabular figures

Text using OpenType proportional and tabular figures

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.

Text using OpenType slashed zero numerals

Text using OpenType slashed zero numerals

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.

Text using OpenType capitals

Text using OpenType capitals

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

AnnotationAlternates

Numeric value - byte

0

Capitals

AllPetiteCaps | AllSmallCaps | Normal | PetiteCaps | SmallCaps | Titling | Unicase

FontCapitals.Normal

CapitalSpacing

Boolean

false

CaseSensitiveForms

Boolean

false

ContextualAlternates

Boolean

true

ContextualLigatures

Boolean

true

ContextualSwashes

Numeric value - byte

0

DiscretionaryLigatures

Boolean

false

EastAsianExpertForms

Boolean

false

EastAsianLanguage

HojoKanji | Jis04 | Jis78 | Jis83 | Jis90 | NlcKanji | Normal | Simplified | Traditional | TraditionalNames

FontEastAsianLanguage.Normal

EastAsianWidths

Full | Half | Normal | Proportional | Quarter | Third

FontEastAsianWidths.Normal

Fraction

Normal | Slashed | Stacked

FontFraction.Normal

HistoricalForms

Boolean

false

HistoricalLigatures

Boolean

false

Kerning

Boolean

true

MathematicalGreek

Boolean

false

NumeralAlignment

Normal | Proportional | Tabular

FontNumeralAlignment.Normal

NumeralStyle

Boolean

FontNumeralStyle.Normal

SlashedZero

Boolean

false

StandardLigatures

Boolean

true

StandardSwashes

numeric value – byte

0

StylisticAlternates

numeric value – byte

0

StylisticSet1

Boolean

false

StylisticSet2

Boolean

false

StylisticSet3

Boolean

false

StylisticSet4

Boolean

false

StylisticSet5

Boolean

false

StylisticSet6

Boolean

false

StylisticSet7

Boolean

false

StylisticSet8

Boolean

false

StylisticSet9

Boolean

false

StylisticSet10

Boolean

false

StylisticSet11

Boolean

false

StylisticSet12

Boolean

false

StylisticSet13

Boolean

false

StylisticSet14

Boolean

false

StylisticSet15

Boolean

false

StylisticSet16

Boolean

false

StylisticSet17

Boolean

false

StylisticSet18

Boolean

false

StylisticSet19

Boolean

false

StylisticSet20

Boolean

false

Variants

Inferior | Normal | Ordinal | Ruby | Subscript | Superscript

FontVariants.Normal




Packaging Fonts with Applications


This topic provides an overview of how to package fonts with your Windows Presentation Foundation (WPF) application.

System_CAPS_noteNote

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>

System_CAPS_noteNote

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.

// The base URI reference can include an application subdirectory.
myTextBlock.FontFamily = new FontFamily(new Uri("pack://application:,,,/resources/"), "./#Pericles Light");

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>
// The font resource reference includes the base Uri (application directory level),
// and the file resource location, which is relative to the base Uri.
myTextBlock.FontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/pages/#Pericles Light");

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".

foreach (Typeface typeface in Fonts.GetTypefaces(new Uri("pack://application:,,,/"), "./resources/"))
{
    // Perform action.
}

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>
System_CAPS_noteNote

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.

List of font names in sample font pack

Fonts in the OpenType Font Pack

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.

System_CAPS_noteNote

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.

Lindsey font (OpenType)

Displaying the Lindsey font

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


This topic describes the GlyphRun object and the 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.

  1. Screen display of fixed-format documents.

  2. 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.

  3. Fixed-format document representation, including clients for previous versions of Windows and other computing devices.

System_CAPS_noteNote

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 FrameworkElementGlyphsGlyphs 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

FontUri

Specifies a resource identifier: file name, Web uniform resource identifier (URI), or resource reference in the application .exe or container.

FontRenderingEmSize

Specifies the font size in drawing surface units (default is .96 inches).

StyleSimulations

Specifies flags for bold and Italic styles.

BidiLevel

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.

Diagraph of glyph measurements

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


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.

Diagram of text decoration locations

Example of text decoration types

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.

Text decoration with linear gradient underline

Example of an underline styled with a linear gradient brush and 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.

Hyperlinks displaying TextDecorations

Hyperlinks defined with TextDecorations

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.

Hyperlinks displaying TextDecorations

Hyperlinks defined with TextDecorations

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.

Text rotated using a RotateTransform

Example of text rotated 90 degrees

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.

Text scaled using a ScaleTransform

Example of scaled text

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>
System_CAPS_noteNote

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.

Text skewed using a SkewTransform

Example of skewed text

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.

Text offset using a TranslateTransform

Example of translated text

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"/>
System_CAPS_noteNote

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.

Text changing opacity from 1.00 to 0.00

Text Opacity changing from 1.00 to 0.00

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.

Text shadow with Softness = 0.25

Example of text with a soft shadow

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>
System_CAPS_noteNote

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.

Text shadow with Softness = 0

Example of text with a hard shadow

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.

DropShadow degree setting of shadow

DropShadow Direction diagram

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

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.

Text shadow using a BlurBitmapEffect

Example of text with a blur effect

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

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.

Text shadow using a TranslateTransform

Example of text using a transform for 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.

Text displayed with a linear gradient brush

Example of a linear gradient brush applied to a text box

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.

Text outline using a linear gradient brush

Example of a linear gradient brush applied to the outline geometry of 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.

The following examples illustrate several ways of creating visual effects by modifying the stroke and fill of converted text.

Text with different colors for fill and stroke

Example of setting stroke and fill to different colors

Text with image brush applied to stroke

Example of an image brush applied to the stroke

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.

Text with image brush applied to stroke

Example of an image brush applied to the stroke and highlight

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.

Controls displaying text as background

Example of controls with custom text backgrounds

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;
}
System_CAPS_noteNote

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

<

&lt;

Less than symbol.

>

&gt;

Greater than sign.

&

&amp;

Ampersand symbol.

"

&quot;

Double quote symbol.

System_CAPS_noteNote

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>
  &lt;    <!-- Less than symbol -->
  &gt;    <!-- Greater than symbol -->
  &amp;   <!-- Ampersand symbol -->
  &quot;  <!-- 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.

The XPS Print System

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.

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.

System_CAPS_noteNote

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.

  1. Create a PrintQueue object for the existing printer that is going to be cloned.

  2. 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.

  3. Create a PrintBooleanProperty object and set its Name to "IsShared" and its Value to true.

  4. Use the PrintBooleanProperty object to be the value of the PrintPropertyDictionary's "IsShared" entry.

  5. Create a PrintStringProperty object and set its Name to "ShareName" and its Value to an appropriate String.

  6. Use the PrintStringProperty object to be the value of the PrintPropertyDictionary's "ShareName" entry.

  7. Create another PrintStringProperty object and set its Name to "Location" and its Value to an appropriate String.

  8. Use the second PrintStringProperty object to be the value of the PrintPropertyDictionary's "Location" entry.

  9. Create an array of Strings. Each item is the name of a port on the server.

  10. 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.

  1. 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.

    1. Obtain a list of all print servers.

    2. Loop through the servers to query their print queues.

    3. Within each pass of the server loop, loop through all the server's queues to query their jobs

    4. Within each pass of the queue loop, loop through its jobs and gather identifying information about those that were submitted by the complaining user.

  2. 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 PrintServerPrintQueue, and PrintSystemJobInfo objects.

At this point the application contains a branching structure corresponding to the two ways of checking a print job's status:

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.

  1. Read the StartTimeOfDay and UntilTimeOfDay properties of the PrintQueue to determine whether the current time is between them.

  2. 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.

System_CAPS_noteNote

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.

  1. 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.

  2. 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.

  3. Enumerate the members of the dictionary. For each of them, do the following.

  4. Up-cast the value of each entry to PrintProperty and use it to create a PrintProperty object.

  5. 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.

  1. Determine if the printer is an XPSDrv printer. (See Printing Overview for more about XPSDrv.)

  2. If the printer is not an XPSDrv printer, set the thread's apartment to single thread.

  3. Instantiate a print server and print queue object.

  4. 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.

  1. Obtain a list of all print servers.

  2. Loop through the servers to query their print queues.

  3. 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.

System_CAPS_noteNote

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:

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.

  1. Determine a printer's capabilities.

  2. Configure a PrintTicket to use those capabilities.

  3. 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.

  1. Get a PrintCapabilities object with the GetPrintCapabilities method.

  2. 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>.

    System_CAPS_noteNote

    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.

  3. 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.

  4. 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.

  5. 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.)

  6. 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
:
Posted by 지훈2
2016. 11. 1. 19:30

Resources 프로그래밍/WPF2016. 11. 1. 19:30

Resources


XAML Resources


https://msdn.microsoft.com/ko-kr/library/ms750613(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/ms750613(v=vs.110).aspx


A resource is an object that can be reused in different places in your application. Examples of resources include brushes and styles. This overview describes how to use resources in XAML. You can also create and access resources by using code, or interchangeably between code and Extensible Application Markup Language (XAML). For more information, see Resources and Code.

System_CAPS_noteNote

The resource files described in this topic are different than the resource files described in WPF Application Resource, Content, and Data Files and different than the embedded or linked resources described in Managing Application Resources (.NET).

Using Resources in XAML

The following example defines a SolidColorBrush as a resource on the root element of a page. The example then references the resource and uses it to set properties of several child elements, including an Ellipse, a TextBlock, and a Button.

<Page Name="root"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
  <Page.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
    <Style TargetType="Border" x:Key="PageBackground">
      <Setter Property="Background" Value="Blue"/>
    </Style>
    <Style TargetType="TextBlock" x:Key="TitleText">
      <Setter Property="Background" Value="Blue"/>
      <Setter Property="DockPanel.Dock" Value="Top"/>
      <Setter Property="FontSize" Value="18"/>
      <Setter Property="Foreground" Value="#4E87D4"/>
      <Setter Property="FontFamily" Value="Trebuchet MS"/>
      <Setter Property="Margin" Value="0,40,10,10"/>
    </Style>
    <Style TargetType="TextBlock" x:Key="Label">
      <Setter Property="DockPanel.Dock" Value="Right"/>
      <Setter Property="FontSize" Value="8"/>
      <Setter Property="Foreground" Value="{StaticResource MyBrush}"/>
      <Setter Property="FontFamily" Value="Arial"/>
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Margin" Value="0,3,10,0"/>
    </Style>
  </Page.Resources>
  <StackPanel>
    <Border Style="{StaticResource PageBackground}">
      <DockPanel>
        <TextBlock Style="{StaticResource TitleText}">Title</TextBlock>
        <TextBlock Style="{StaticResource Label}">Label</TextBlock>
        <TextBlock DockPanel.Dock="Top" HorizontalAlignment="Left" FontSize="36" Foreground="{StaticResource MyBrush}" Text="Text" Margin="20" />
        <Button DockPanel.Dock="Top" HorizontalAlignment="Left" Height="30" Background="{StaticResource MyBrush}" Margin="40">Button</Button>
        <Ellipse DockPanel.Dock="Top" HorizontalAlignment="Left" Width="100" Height="100" Fill="{StaticResource MyBrush}" Margin="40" />
      </DockPanel>
    </Border>
  </StackPanel>
</Page>


Every framework-level element ( FrameworkElement or FrameworkContentElement) has a Resources property, which is the property that contains the resources (as a ResourceDictionary) that a resource defines. You can define resources on any element. However, resources are most often defined on the root element, which is Page in the example.

Each resource in a resource dictionary must have a unique key. When you define resources in markup, you assign the unique key through the x:Key Directive. Typically, the key is a string; however, you can also set it to other object types by using the appropriate markup extensions. Nonstring keys for resources are used by certain feature areas in WPF, notably for styles, component resources, and data styling.

After you define a resource, you can reference the resource to be used for a property value by using a resource markup extension syntax that specifies the key name, for example:

<Button Background="{StaticResource MyBrush}"/>
<Ellipse Fill="{StaticResource MyBrush}"/>

In the preceding example, when the XAML loader processes the value {StaticResource MyBrush} for the  Background property on  Button, the resource lookup logic first checks the resource dictionary for the Button element. If Button does not have a definition of the resource key MyBrush (it does not; its resource collection is empty), the lookup next checks the parent element of Button, which is  Page. Thus, when you define a resource on the  Page root element, all the elements in the logical tree of the  Page can access it, and you can reuse the same resource for setting the value of any property that accepts the Type that the resource represents. In the previous example, the same MyBrush resource sets two different properties: the Background of a  Button, and the Fill of a Rectangle.

Static and Dynamic Resources

A resource can be referenced as either a static resource or a dynamic resource. This is done by using either the StaticResource Markup Extension or the DynamicResource Markup Extension. A markup extension is a feature of XAML whereby you can specify an object reference by having the markup extension process the attribute string and return the object to a XAML loader. For more information about markup extension behavior, seeMarkup Extensions and WPF XAML.

When you use a markup extension, you typically provide one or more parameters in string form that are processed by that particular markup extension, rather than being evaluated in the context of the property being set. The StaticResource Markup Extension processes a key by looking up the value for that key in all available resource dictionaries. This happens during loading, which is the point in time when the loading process needs to assign the property value that takes the static resource reference. The DynamicResource Markup Extension instead processes a key by creating an expression, and that expression remains unevaluated until the application is actually run, at which time the expression is evaluated and provides a value.

When you reference a resource, the following considerations can influence whether you use a static resource reference or a dynamic resource reference:

  • The overall design of how you create the resources for your application (per page, in the application, in loose XAML, in a resource only assembly).

  • The application functionality: is updating resources in real time part of your application requirements?

  • The respective lookup behavior of that resource reference type.

  • The particular property or resource type, and the native behavior of those types.

Static Resources

Static resource references work best for the following circumstances:

  • Your application design concentrates most of all of its resources into page or application level resource dictionaries. Static resource references are not reevaluated based on runtime behaviors such as reloading a page, and therefore there can be some performance benefit to avoiding large numbers of dynamic resource references when they are not necessary per your resource and application design.

  • You are setting the value of a property that is not on a DependencyObject or a Freezable.

  • You are creating a resource dictionary that will be compiled into a DLL, and packaged as part of the application or shared between applications.

  • You are creating a theme for a custom control, and are defining resources that are used within the themes. For this case, you typically do not want the dynamic resource reference lookup behavior, you instead want the static resource reference behavior so that the lookup is predictable and self-contained to the theme. With a dynamic resource reference, even a reference within a theme is left unevaluated until runtime, and there is a chance that when the theme is applied, some local element will redefine a key that your theme is trying to reference, and the local element will fall prior to the theme itself in the lookup. If that happens, your theme will not behave in an expected manner.

  • You are using resources to set large numbers of dependency properties. Dependency properties have effective value caching as enabled by the property system, so if you provide a value for a dependency property that can be evaluated at load time, the dependency property does not have to check for a reevaluated expression and can return the last effective value. This technique can be a performance benefit.

  • You want to change the underlying resource for all consumers, or you want to maintain separate writable instances for each consumer by using the x:Shared Attribute.

Static resource lookup behavior

  1. The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.

  2. The lookup process then traverses the logical tree upward, to the parent element and its resource dictionary. This continues until the root element is reached.

  3. Next, application resources are checked. Application resources are those resources within the resource dictionary that is defined by theApplication object for your WPF application.

Static resource references from within a resource dictionary must reference a resource that has already been defined lexically before the resource reference. Forward references cannot be resolved by a static resource reference. For this reason, if you use static resource references, you must design your resource dictionary structure such that resources intended for by-resource use are defined at or near the beginning of each respective resource dictionary.

Static resource lookup can extend into themes, or into system resources, but this is supported only because the XAML loader defers the request. The deferral is necessary so that the runtime theme at the time the page loads applies properly to the application. However, static resource references to keys that are known to only exist in themes or as system resources are not recommended. This is because such references are not reevaluated if the theme is changed by the user in realtime. A dynamic resource reference is more reliable when you request theme or system resources. The exception is when a theme element itself requests another resource. These references should be static resource references, for the reasons mentioned earlier.

The exception behavior if a static resource reference is not found varies. If the resource was deferred, then the exception occurs at runtime. If the resource was not deferred, the exception occurs at load time.

Dynamic Resources

Dynamic resources work best for the following circumstances:

  • The value of the resource depends on conditions that are not known until runtime. This includes system resources, or resources that are otherwise user settable. For example, you can create setter values that refer to system properties, as exposed by SystemColors,SystemFonts, or SystemParameters. These values are truly dynamic because they ultimately come from the runtime environment of the user and operating system. You might also have application-level themes that can change, where page-level resource access must also capture the change.

  • You are creating or referencing theme styles for a custom control.

  • You intend to adjust the contents of a ResourceDictionary during an application lifetime.

  • You have a complicated resource structure that has interdependencies, where a forward reference may be required. Static resource references do not support forward references, but dynamic resource references do support them because the resource does not need to be evaluated until runtime , and forward references are therefore not a relevant concept.

  • You are referencing a resource that is particularly large from the perspective of a compile or working set, and the resource might not be used immediately when the page loads. Static resource references always load from XAML when the page loads; however, a dynamic resource reference does not load until it is actually used.

  • You are creating a style where setter values might come from other values that are influenced by themes or other user settings.

  • You are applying resources to elements that might be reparented in the logical tree during application lifetime. Changing the parent also potentially changes the resource lookup scope, so if you want the resource for a reparented element to be reevaluated based on the new scope, always use a dynamic resource reference.

Dynamic resource lookup behavior

Resource lookup behavior for a dynamic resource reference parallels the lookup behavior in your code if you call FindResource orSetResourceReference.

  1. The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.

  2. The lookup process then traverses the logical tree upward, to the parent element and its resource dictionary. This continues until the root element is reached.

  3. Next, application resources are checked. Application resources are those resources within the resource dictionary that is defined by theApplication object for your WPF application.

  4. Theme resource dictionary is checked, for the currently active theme. If the theme changes at runtime, the value is reevaluated.

  5. System resources are checked.

Exception behavior (if any) varies:

  • If a resource was requested by a FindResource call, and was not found, an exception is raised.

  • If a resource was requested by a TryFindResource call, and was not found, no exception is raised, but the returned value is null. If the property being set does not accept null, then it is still possible that a deeper exception will be raised (this depends on the individual property being set).

  • If a resource was requested by a dynamic resource reference in XAML, and was not found, then the behavior depends on the general property system, but the general behavior is as if no property setting operation occurred at the level where the resource exists. For instance, if you attempt to set the background on a an individual button element using a resource that could not be evaluated, then no value set results, but the effective value can still come from other participants in the property system and value precedence. For instance, the background value might still come from a locally defined button style, or from the theme style. For properties that are not defined by theme styles, the effective value after a failed resource evaluation might come from the default value in the property metadata.

Restrictions

Dynamic resource references have some notable restrictions. At least one of the following must be true:

Because the property being set must be a DependencyProperty or Freezable property, most property changes can propagate to UI because a property change (the changed dynamic resource value) is acknowledged by the property system. Most controls include logic that will force another layout of a control if a DependencyProperty changes and that property might affect layout. However, not all properties that have aDynamicResource Markup Extension as their value are guaranteed to provide the value in such a way that they update in realtime in the UI. That functionality still might vary depending on the property, as well as depending on the type that owns the property, or even the logical structure of your application.

Styles, DataTemplates, and Implicit Keys

Earlier, it was stated that all items in a ResourceDictionary must have a key. However, that does not mean that all resources must have an explicitx:Key. Several object types support an implicit key when defined as a resource, where the key value is tied to the value of another property. This is known as an implicit key, whereas an x:Key attribute is an explicit key. You can overwrite any implicit key by specifying an explicit key.

One very important scenario for resources is when you define a Style. In fact, a Style is almost always defined as an entry in a resource dictionary, because styles are inherently intended for reuse. For more information about styles, see Styling and Templating.

Styles for controls can be both created with and referenced with an implicit key. The theme styles that define the default appearance of a control rely on this implicit key. The implicit key from the standpoint of requesting it is the Type of the control itself. The implicit key from the standpoint of defining the resource is the TargetType of the style. Therefore, if you are creating themes for custom controls, creating styles that interact with existing theme styles, you do not need to specify an x:Key Directive for that Style. And if you want to use the themed styles, you do not need to specify any style at all. For instance, the following style definition works, even though the Style resource does not appear to have a key:

<Style TargetType="Button">
  <Setter Property="Background">
    <Setter.Value>
      <LinearGradientBrush>
        <GradientStop Offset="0.0" Color="AliceBlue"/>
        <GradientStop Offset="1.0" Color="Salmon"/>           
      </LinearGradientBrush>
    </Setter.Value>
  </Setter>  
  <Setter Property="FontSize" Value="18"/>
</Style>

That style really does have a key: the implicit key typeof(Button). In markup, you can specify a TargetType directly as the type name (or you can optionally use {x:Type...} to return a Type.

Through the default theme style mechanisms used by WPF, that style is applied as the runtime style of a Button on the page, even though the Buttonitself does not attempt to specify its Style property or a specific resource reference to the style. Your style defined in the page is found earlier in the lookup sequence earlier than the theme dictionary style, using the same key that the theme dictionary style has. You could just specify<Button>Hello</Button> anywhere in the page, and the style you defined with TargetType of Button would apply to that button. If you want, you can still explicitly key the style with the same type value as TargetType, for clarity in your markup, but that is optional.

Implicit keys for styles do not apply on a control if OverridesDefaultStyle is true (also note that OverridesDefaultStyle might be set as part of native behavior for the control class, rather than explicitly on an instance of the control). Also, in order to support implicit keys for derived class scenarios, the control must override DefaultStyleKey (all existing controls provided as part of WPF do this). For more information about styles, themes, and control design, see Guidelines for Designing Stylable Controls.

DataTemplate also has an implicit key. The implicit key for a DataTemplate is the DataType property value. DataType can also be specified as the name of the type rather than explicitly using {x:Type...}. For details, see Data Templating Overview.




Resources and Code


This overview concentrates on how Windows Presentation Foundation (WPF) resources can be accessed or created using code rather than Extensible Application Markup Language (XAML) syntax. For more information on general resource usage and resources from a XAML syntax perspective, see XAML Resources.

Accessing Resources from Code

The keys that identify resources if they are defined through XAML are also used to retrieve specific resources if you request the resource in code. The simplest way to retrieve a resource from code is to call either the FindResource or the TryFindResource method from framework-level objects in your application. The behavioral difference between these methods is what happens if the requested key is not found. FindResource raises an exception; TryFindResource will not raise an exception but returns null. Each method takes the resource key as an input parameter, and returns a loosely typed object. Typically, a resource key is a string, but there are occasional nonstring usages; see the Using Objects as Keys section for details. Typically you would cast the returned object to the type required by the property that you are setting when requesting the resource. The lookup logic for code resource resolution is the same as the dynamic resource reference XAML case. The search for resources starts from the calling element, then continues to successive parent elements in the logical tree. The lookup continues onwards into application resources, themes, and system resources if necessary. A code request for a resource will properly account for runtime changes in resource dictionaries that might have been made subsequent to that resource dictionary being loaded from XAML, and also for realtime system resource changes.

The following is a brief code example that finds a resource by key and uses the returned value to set a property, implemented as a Click event handler.

void SetBGByResource(object sender, RoutedEventArgs e)
{
  Button b = sender as Button;
  b.Background = (Brush)this.FindResource("RainbowBrush");
}

An alternative method for assigning a resource reference is SetResourceReference. This method takes two parameters: the key of the resource, and the identifier for a particular dependency property that is present on the element instance to which the resource value should be assigned. Functionally, this method is the same and has the advantage of not requiring any casting of return values.

Still another way to access resources programmatically is to access the contents of the Resources property as a dictionary. Accessing the dictionary contained by this property is also how you can add new resources to existing collections, check to see if a given key name is already taken in the collection, and other dictionary/collection operations. If you are writing a WPF application entirely in code, you can also create the entire collection in code, assign keys to it, and then assign the finished collection to the Resources property of an established element. This will be described in the next section.

You can index within any given Resources collection, using a specific key as the index, but you should be aware that accessing the resource in this way does not follow the normal runtime rules of resource resolution. You are only accessing that particular collection. Resource lookup will not be traversing the scope to the root or the application if no valid object was found at the requested key. However, this approach may have performance advantages in some cases precisely because the scope of the search for the key is more constrained. See the ResourceDictionary class for more details on how to work with the resource dictionary directly.

Creating Resources with Code

If you want to create an entire WPF application in code, you might also want to create any resources in that application in code. To achieve this, create a new ResourceDictionary instance, and then add all the resources to the dictionary using successive calls to ResourceDictionary.Add. Then, use the ResourceDictionary thus created to set the Resources property on an element that is present in a page scope, or the Application.Resources. You could also maintain the ResourceDictionary as a standalone object without adding it to an element. However, if you do this, you must access the resources within it by item key, as if it were a generic dictionary. A ResourceDictionary that is not attached to an element Resources property would not exist as part of the element tree and has no scope in a lookup sequence that can be used by FindResource and related methods.

Using Objects as Keys

Most resource usages will set the key of the resource to be a string. However, various WPF features deliberately do not use a string type to specify keys, instead this parameter is an object. The capability of having the resource be keyed by an object is used by the WPF style and theming support. The styles in themes which become the default style for an otherwise non-styled control are each keyed by the Type of the control that they should apply to. Being keyed by type provides a reliable lookup mechanism that works on default instances of each control type, and type can be detected by reflection and used for styling derived classes even though the derived type otherwise has no default style. You can specify a Type key for a resource defined in XAML by using the x:Type Markup Extension. Similar extensions exist for other nonstring key usages that support WPF features, such as ComponentResourceKey Markup Extension.





Merged Resource Dictionaries


Windows Presentation Foundation (WPF) resources support a merged resource dictionary feature. This feature provides a way to define the resources portion of a WPF application outside of the compiled XAML application. Resources can then be shared across applications and are also more conveniently isolated for localization.

Introducing a Merged Resource Dictionary

In markup, you use the following syntax to introduce a merged resource dictionary into a page:

<Page.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="myresourcedictionary.xaml"/>
      <ResourceDictionary Source="myresourcedictionary2.xaml"/>
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Page.Resources>

Note that the ResourceDictionary element does not have an x:Key Directive, which is generally required for all items in a resource collection. But another ResourceDictionary reference within the MergedDictionaries collection is a special case, reserved for this merged resource dictionary scenario. The ResourceDictionary that introduces a merged resource dictionary cannot have an x:Key Directive. Typically, each ResourceDictionarywithin the MergedDictionaries collection specifies a Source attribute. The value of Source should be a uniform resource identifier (URI) that resolves to the location of the resources file to be merged. The destination of that URI must be another XAML file, with ResourceDictionary as its root element.

System_CAPS_noteNote

It is legal to define resources within a ResourceDictionary that is specified as a merged dictionary, either as an alternative to specifying Source, or in addition to whatever resources are included from the specified source. However, this is not a common scenario; the main scenario for merged dictionaries is to merge resources from external file locations. If you want to specify resources within the markup for a page, you should typically define these in the main ResourceDictionary and not in the merged dictionaries.

Merged Dictionary Behavior

Resources in a merged dictionary occupy a location in the resource lookup scope that is just after the scope of the main resource dictionary they are merged into. Although a resource key must be unique within any individual dictionary, a key can exist multiple times in a set of merged dictionaries. In this case, the resource that is returned will come from the last dictionary found sequentially in the MergedDictionaries collection. If the MergedDictionaries collection was defined in XAML, then the order of the merged dictionaries in the collection is the order of the elements as provided in the markup. If a key is defined in the primary dictionary and also in a dictionary that was merged, then the resource that is returned will come from the primary dictionary. These scoping rules apply equally for both static resource references and dynamic resource references.

Merged Dictionaries and Code

Merged dictionaries can be added to a Resources dictionary through code. The default, initially empty ResourceDictionary that exists for any Resources property also has a default, initially empty MergedDictionaries collection property. To add a merged dictionary through code, you obtain a reference to the desired primary ResourceDictionary, get its MergedDictionaries property value, and call Add on the generic Collectionthat is contained in MergedDictionaries. The object you add must be a new ResourceDictionary. In code, you do not set the Source property. Instead, you must obtain a ResourceDictionary object by either creating one or loading one. One way to load an existing ResourceDictionary to call XamlReader.Load on an existing XAML file stream that has a ResourceDictionary root, then casting the XamlReader.Load return value to ResourceDictionary.

Merged Resource Dictionary URIs

There are several techniques for how to include a merged resource dictionary, which are indicated by the uniform resource identifier (URI) format that you will use. Broadly speaking, these techniques can be divided into two categories: resources that are compiled as part of the project, and resources that are not compiled as part of the project.

For resources that are compiled as part of the project, you can use a relative path that refers to the resource location. The relative path is evaluated during compilation. Your resource must be defined as part of the project as a Resource build action. If you include a resource .xaml file in the project as Resource, you do not need to copy the resource file to the output directory, the resource is already included within the compiled application. You can also use Content build action, but you must then copy the files to the output directory and also deploy the resource files in the same path relationship to the executable.

System_CAPS_noteNote

Do not use the Embedded Resource build action. The build action itself is supported for WPF applications, but the resolution of Source does not incorporate ResourceManager, and thus cannot separate the individual resource out of the stream. You could still use Embedded Resource for other purposes so long as you also used ResourceManager to access the resources.

A related technique is to use a Pack URI to a XAML file, and refer to it as Source. Pack URI enables references to components of referenced assemblies and other techniques. For more information on Pack URIs, see WPF Application Resource, Content, and Data Files.

For resources that are not compiled as part of the project, the URI is evaluated at run time. You can use a common URI transport such as file: or http: to refer to the resource file. The disadvantage of using the noncompiled resource approach is that file: access requires additional deployment steps, and http: access implies the Internet security zone.

Reusing Merged Dictionaries

You can reuse or share merged resource dictionaries between applications, because the resource dictionary to merge can be referenced through any valid uniform resource identifier (URI). Exactly how you do this will depend on your application deployment strategy and which application model you follow. The aforementioned Pack URI strategy provides a way to commonly source a merged resource across multiple projects during development by sharing an assembly reference. In this scenario the resources are still distributed by the client, and at least one of the applications must deploy the referenced assembly. It is also possible to reference merged resources through a distributed URI that uses the http protocol.

Writing merged dictionaries as local application files or to local shared storage is another possible merged dictionary / application deployment scenario.

Localization

If resources that need to be localized are isolated to dictionaries that are merged into primary dictionaries, and kept as loose XAML, these files can be localized separately. This technique is a lightweight alternative to localizing the satellite resource assemblies. For details, see WPF Globalization and Localization Overview.




How-to Topics


How to: Define and Reference a Resource


This example shows how to define a resource and reference it by using an attribute in Extensible Application Markup Language (XAML).

Example

The following example defines two types of resources: a SolidColorBrush resource, and several Style resources. The SolidColorBrush resource MyBrush is used to provide the value of several properties that each take a Brush type value. The Style resources PageBackgroundTitleText and Label each target a particular control type. The styles set a variety of different properties on the targeted controls, when that style resource is referenced by resource key and is used to set the Style property of several specific control elements defined in XAML.

Note that one of the properties within the setters of the Label style also references the MyBrush resource defined earlier. This is a common technique, but it is important to remember that resources are parsed and entered into a resource dictionary in the order that they are given. Resources are also requested by the order found within the dictionary if you use the StaticResource Markup Extension to reference them from within another resource. Make sure that any resource that you reference is defined earlier within the resources collection than where that resource is then requested. If necessary, you can work around the strict creation order of resource refererences by using a DynamicResource Markup Extension to reference the resource at runtime instead, but you should be aware that this DynamicResource technique has performance consequences. For details, see XAML Resources.

<Page Name="root"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
    <Style TargetType="Border" x:Key="PageBackground">
      <Setter Property="Background" Value="Blue"/>
    </Style>
    <Style TargetType="TextBlock" x:Key="TitleText">
      <Setter Property="Background" Value="Blue"/>
      <Setter Property="DockPanel.Dock" Value="Top"/>
      <Setter Property="FontSize" Value="18"/>
      <Setter Property="Foreground" Value="#4E87D4"/>
      <Setter Property="FontFamily" Value="Trebuchet MS"/>
      <Setter Property="Margin" Value="0,40,10,10"/>
    </Style>
    <Style TargetType="TextBlock" x:Key="Label">
      <Setter Property="DockPanel.Dock" Value="Right"/>
      <Setter Property="FontSize" Value="8"/>
      <Setter Property="Foreground" Value="{StaticResource MyBrush}"/>
      <Setter Property="FontFamily" Value="Arial"/>
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Margin" Value="0,3,10,0"/>
    </Style>
  </Page.Resources>
  <StackPanel>
    <Border Style="{StaticResource PageBackground}">
      <DockPanel>
        <TextBlock Style="{StaticResource TitleText}">Title</TextBlock>
        <TextBlock Style="{StaticResource Label}">Label</TextBlock>
        <TextBlock DockPanel.Dock="Top" HorizontalAlignment="Left" FontSize="36" Foreground="{StaticResource MyBrush}" Text="Text" Margin="20" />
        <Button DockPanel.Dock="Top" HorizontalAlignment="Left" Height="30" Background="{StaticResource MyBrush}" Margin="40">Button</Button>
        <Ellipse DockPanel.Dock="Top" HorizontalAlignment="Left" Width="100" Height="100" Fill="{StaticResource MyBrush}" Margin="40" />
      </DockPanel>
    </Border>
  </StackPanel>
</Page>




How to: Use Application Resources


This example shows how to use application resources.

Example

The following example shows an application definition file. The application definition file defines a resource section (a value for the Resources property). Resources defined at the application level can be accessed by all other pages that are part of the application. In this case, the resource is a declared style. Because a complete style that includes a control template can be lengthy, this example omits the control template that is defined within theContentTemplate property setter of the style.

<Application.Resources>
  <Style TargetType="Button" x:Key="GelButton" >
    <Setter Property="Margin" Value="1,2,1,2"/>
    <Setter Property="HorizontalAlignment" Value="Left"/>
    <Setter Property="Template">
      <Setter.Value>
      </Setter.Value>
    </Setter>
  </Style>
</Application.Resources>

The following example shows a XAML page that references the application-level resource that the previous example defined. The resource is referenced by using a StaticResource Markup Extension that specifies the unique resource key for the requested resource. No resource with key of "GelButton" is found in the current page, so the resource lookup scope for the requested resource continues beyond the current page and into the defined application-level resources.

<StackPanel
  Name="root"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >
  <Button Height="50" Width="250" Style="{StaticResource GelButton}" Content="Button 1" />
  <Button Height="50" Width="250" Style="{StaticResource GelButton}" Content="Button 2" />
</StackPanel>




How to: Use SystemFonts


This example shows how to use the static resources of the SystemFonts class in order to style or customize a button.

Example

System resources expose several system-determined values as both resources and properties in order to help you create visuals that are consistent with system settings. SystemFonts is a class that contains both system font values as static properties, and properties that reference resource keys that can be used to access those values dynamically at run time. For example, CaptionFontFamily is a SystemFonts value, and CaptionFontFamilyKey is a corresponding resource key.

In XAML, you can use the members of SystemFonts as either static properties or dynamic resource references (with the static property value as the key). Use a dynamic resource reference if you want the font metric to automatically update while the application runs; otherwise, use a static value reference.

System_CAPS_noteNote

The resource keys have the suffix "Key" appended to the property name.

The following example shows how to access and use the properties of SystemFonts as static values in order to style or customize a button. This markup example assigns SystemFonts values to a button.

<Button Margin="10, 10, 5, 5" Grid.Column="0" Grid.Row="3"      
     FontSize="{x:Static SystemFonts.IconFontSize}"
     FontWeight="{x:Static SystemFonts.MessageFontWeight}"
     FontFamily="{x:Static SystemFonts.CaptionFontFamily}">
     SystemFonts
</Button>

To use the values of SystemFonts in code, you do not have to use either a static value or a dynamic resource reference. Instead, use the non-key properties of the SystemFonts class. Although the non-key properties are apparently defined as static properties, the run-time behavior of WPF as hosted by the system will reevaluate the properties in real time and will properly account for user-driven changes to system values. The following example shows how to specify the font settings of a button.

Button btncsharp = new Button();
btncsharp.Content = "SystemFonts";
btncsharp.Background = SystemColors.ControlDarkDarkBrush;
btncsharp.FontSize = SystemFonts.IconFontSize;
btncsharp.FontWeight = SystemFonts.MessageFontWeight;
btncsharp.FontFamily = SystemFonts.CaptionFontFamily;
cv1.Children.Add(btncsharp);




How to: Use System Fonts Keys


System resources expose a number of system metrics as resources to help developers create visuals that are consistent with system settings.SystemFonts is a class that contains both system font values and system font resources that bind to the values—for example, CaptionFontFamily andCaptionFontFamilyKey.

System font metrics can be used as either static or dynamic resources. Use a dynamic resource if you want the font metric to update automatically while the application runs; otherwise use a static resource.

System_CAPS_noteNote

Dynamic resources have the keyword Key appended to the property name.

The following example shows how to access and use system font dynamic resources to style or customize a button. This XAML example creates a button style that assigns SystemFonts values to a button.

Example

<Style x:Key="SimpleFont" TargetType="{x:Type Button}">
    <Setter Property = "FontSize" Value= "{DynamicResource {x:Static SystemFonts.IconFontSizeKey}}"/>
    <Setter Property = "FontWeight" Value= "{DynamicResource {x:Static SystemFonts.MessageFontWeightKey}}"/>
    <Setter Property = "FontFamily" Value= "{DynamicResource {x:Static SystemFonts.CaptionFontFamilyKey}}"/>
</Style>




How to: Use SystemParameters


This example shows how to access and use the properties of SystemParameters in order to style or customize a button.

Example

System resources expose several system based settings as resources in order to help you create visuals that are consistent with system settings.SystemParameters is a class that contains both system parameter value properties, and resource keys that bind to the values. For example,FullPrimaryScreenHeight is a SystemParameters property value and FullPrimaryScreenHeightKey is the corresponding resource key.

In XAML, you can use the members of SystemParameters as either a static property usage, or a dynamic resource references (with the static property value as the key). Use a dynamic resource reference if you want the system based value to update automatically while the application runs; otherwise, use a static reference. Resource keys have the suffix  Key appended to the property name.

The following example shows how to access and use the static values of SystemParameters to style or customize a button. This markup example sizes a button by applying SystemParameters values to a button.

<Button FontSize="8" Margin="10, 10, 5, 5" Grid.Column="0" Grid.Row="5"      
     HorizontalAlignment="Left" 
     Height="{x:Static SystemParameters.CaptionHeight}"
     Width="{x:Static SystemParameters.IconGridWidth}">
     SystemParameters
</Button>

To use the values of SystemParameters in code, you do not have to use either static references or dynamic resource references. Instead, use the values of the SystemParameters class. Although the non-key properties are apparently defined as static properties, the runtime behavior of WPF as hosted by the system will reevaluate the properties in realtime, and will properly account for user-driven changes to system values. The following example shows how to set the width and height of a button by using SystemParameters values.

Button btncsharp = new Button();
btncsharp.Content = "SystemParameters";
btncsharp.FontSize = 8;
btncsharp.Background = SystemColors.ControlDarkDarkBrush;
btncsharp.Height = SystemParameters.CaptionHeight;
btncsharp.Width = SystemParameters.IconGridWidth;
cv2.Children.Add(btncsharp);




How to: Use System Parameters Keys


System resources expose a number of system metrics as resources to help developers create visuals that are consistent with system settings.SystemParameters is a class that contains both system parameter values and resource keys that bind to the values—for example,FullPrimaryScreenHeight and FullPrimaryScreenHeightKey. System parameter metrics can be used as either static or dynamic resources. Use a dynamic resource if you want the parameter metric to update automatically while the application runs; otherwise use a static resource.

System_CAPS_noteNote

Dynamic resources have the keyword Key appended to the property name.

The following example shows how to access and use system parameter dynamic resources to style or customize a button. This XAML example sizes a button by assigning SystemParameters values to the button's width and height.

Example

<Style x:Key="SimpleParam" TargetType="{x:Type Button}">
    <Setter Property = "Height" Value= "{DynamicResource {x:Static SystemParameters.CaptionHeightKey}}"/>
    <Setter Property = "Width" Value= "{DynamicResource {x:Static SystemParameters.IconGridWidthKey}}"/>
</Style>









'프로그래밍 > WPF' 카테고리의 다른 글

Globalization and Localization  (0) 2016.11.05
Documents  (0) 2016.11.05
Events  (0) 2016.11.01
Element Tree and Serialization  (0) 2016.11.01
Base Elements (기본 요소)  (0) 2016.10.24
:
Posted by 지훈2
2016. 11. 1. 19:22

Events 프로그래밍/WPF2016. 11. 1. 19:22

Events



Routed Events Overview


https://msdn.microsoft.com/ko-kr/library/ms742806(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/ms742806(v=vs.110).aspx


This topic describes the concept of routed events in Windows Presentation Foundation (WPF). The topic defines routed events terminology, describes how routed events are routed through a tree of elements, summarizes how you handle routed events, and introduces how to create your own custom routed events. 

Prerequisites

This topic assumes that you have basic knowledge of the common language runtime (CLR) and object-oriented programming, as well as the concept of how the relationships between WPF elements can be conceptualized as a tree. In order to follow the examples in this topic, you should also understand Extensible Application Markup Language (XAML) and know how to write very basic WPF applications or pages. For more information, see Walkthrough: My First WPF Desktop Application and XAML Overview (WPF).

What Is a Routed Event?

You can think about routed events either from a functional or implementation perspective. Both definitions are presented here, because some people find one or the other definition more useful.

Functional definition: A routed event is a type of event that can invoke handlers on multiple listeners in an element tree, rather than just on the object that raised the event.

Implementation definition: A routed event is a CLR event that is backed by an instance of the RoutedEvent class and is processed by the Windows Presentation Foundation (WPF) event system.

A typical WPF application contains many elements. Whether created in code or declared in XAML, these elements exist in an element tree relationship to each other. The event route can travel in one of two directions depending on the event definition, but generally the route travels from the source element and then "bubbles" upward through the element tree until it reaches the element tree root (typically a page or a window). This bubbling concept might be familiar to you if you have worked with the DHTML object model previously.

Consider the following simple element tree:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

This element tree produces something like the following:

Yes, No, and Cancel buttons

In this simplified element tree, the source of a Click event is one of the Button elements, and whichever Button was clicked is the first element that has the opportunity to handle the event. But if no handler attached to the Button acts on the event, then the event will bubble upwards to the Buttonparent in the element tree, which is the StackPanel. Potentially, the event bubbles to Border, and then beyond to the page root of the element tree (not shown).

In other words, the event route for this Click event is:

Button-->StackPanel-->Border-->...

Top-level Scenarios for Routed Events

The following is a brief summary of the scenarios that motivated the routed event concept, and why a typical CLR event was not adequate for these scenarios:

Control composition and encapsulation: Various controls in WPF have a rich content model. For example, you can place an image inside of a Button, which effectively extends the visual tree of the button. However, the added image must not break the hit-testing behavior that causes a button to respond to a Click of its content, even if the user clicks on pixels that are technically part of the image.

Singular handler attachment points: In Windows Forms, you would have to attach the same handler multiple times to process events that could be raised from multiple elements. Routed events enable you to attach that handler only once, as was shown in the previous example, and use handler logic to determine where the event came from if necessary. For instance, this might be the handler for the previously shown XAML:

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}

Class handling: Routed events permit a static handler that is defined by the class. This class handler has the opportunity to handle an event before any attached instance handlers can.

Referencing an event without reflection: Certain code and markup techniques require a way to identify a specific event. A routed event creates a RoutedEvent field as an identifier, which provides a robust event identification technique that does not require static or run-time reflection.

How Routed Events Are Implemented

routed event is a CLR event that is backed by an instance of the RoutedEvent class and registered with the WPF event system. The RoutedEventinstance obtained from registration is typically retained as a public static readonly field member of the class that registers and thus "owns" the routed event. The connection to the identically named CLR event (which is sometimes termed the "wrapper" event) is accomplished by overriding the add and remove implementations for the CLR event. Ordinarily, the add and remove are left as an implicit default that uses the appropriate language-specific event syntax for adding and removing handlers of that event. The routed event backing and connection mechanism is conceptually similar to how a dependency property is a CLR property that is backed by the DependencyProperty class and registered with the WPF property system.

The following example shows the declaration for a custom Tap routed event, including the registration and exposure of the RoutedEvent identifier field and the add and remove implementations for the Tap CLR event.

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); } 
        remove { RemoveHandler(TapEvent, value); }
}

Routed Event Handlers and XAML

To add a handler for an event using XAML, you declare the event name as an attribute on the element that is an event listener. The value of the attribute is the name of your implemented handler method, which must exist in the partial class of the code-behind file.

<Button Click="b1SetColor">button</Button>

The XAML syntax for adding standard CLR event handlers is the same for adding routed event handlers, because you are really adding handlers to the CLR event wrapper, which has a routed event implementation underneath. For more information about adding event handlers in XAML, see XAML Overview (WPF).

Routing Strategies

Routed events use one of three routing strategies:

  • Bubbling: Event handlers on the event source are invoked. The routed event then routes to successive parent elements until reaching the element tree root. Most routed events use the bubbling routing strategy. Bubbling routed events are generally used to report input or state changes from distinct controls or other UI elements.

  • Direct: Only the source element itself is given the opportunity to invoke handlers in response. This is analogous to the "routing" that Windows Forms uses for events. However, unlike a standard CLR event, direct routed events support class handling (class handling is explained in an upcoming section) and can be used by EventSetter and EventTrigger.

  • Tunneling: Initially, event handlers at the element tree root are invoked. The routed event then travels a route through successive child elements along the route, towards the node element that is the routed event source (the element that raised the routed event). Tunneling routed events are often used or handled as part of the compositing for a control, such that events from composite parts can be deliberately suppressed or replaced by events that are specific to the complete control. Input events provided in WPF often come implemented as a tunneling/bubbling pair. Tunneling events are also sometimes referred to as Preview events, because of a naming convention that is used for the pairs.

Why Use Routed Events?

As an application developer, you do not always need to know or care that the event you are handling is implemented as a routed event. Routed events have special behavior, but that behavior is largely invisible if you are handling an event on the element where it is raised.

Where routed events become powerful is if you use any of the suggested scenarios: defining common handlers at a common root, compositing your own control, or defining your own custom control class.

Routed event listeners and routed event sources do not need to share a common event in their hierarchy. Any UIElement or ContentElement can be an event listener for any routed event. Therefore, you can use the full set of routed events available throughout the working API set as a conceptual "interface" whereby disparate elements in the application can exchange event information. This "interface" concept for routed events is particularly applicable for input events.

Routed events can also be used to communicate through the element tree, because the event data for the event is perpetuated to each element in the route. One element could change something in the event data, and that change would be available to the next element in the route.

Other than the routing aspect, there are two other reasons that any given WPF event might be implemented as a routed event instead of a standard CLR event. If you are implementing your own events, you might also consider these principles:

  • Certain WPF styling and templating features such as EventSetter and EventTrigger require the referenced event to be a routed event. This is the event identifier scenario mentioned earlier.

  • Routed events support a class handling mechanism whereby the class can specify static methods that have the opportunity to handle routed events before any registered instance handlers can access them. This is very useful in control design, because your class can enforce event-driven class behaviors that cannot be accidentally suppressed by handling an event on an instance.

Each of the above considerations is discussed in a separate section of this topic.

Adding and Implementing an Event Handler for a Routed Event

To add an event handler in XAML, you simply add the event name to an element as an attribute and set the attribute value as the name of the event handler that implements an appropriate delegate, as in the following example.

<Button Click="b1SetColor">button</Button>

b1SetColor is the name of the implemented handler that contains the code that handles the Click event. b1SetColor must have the same signature as the RoutedEventHandler delegate, which is the event handler delegate for the Click event. The first parameter of all routed event handler delegates specifies the element to which the event handler is added, and the second parameter specifies the data for the event.

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event

RoutedEventHandler is the basic routed event handler delegate. For routed events that are specialized for certain controls or scenarios, the delegates to use for the routed event handlers also might become more specialized, so that they can transmit specialized event data. For instance, in a common input scenario, you might handle a DragEnter routed event. Your handler should implement the DragEventHandler delegate. By using the most specific delegate, you can process the DragEventArgs in the handler and read the Data property, which contains the clipboard payload of the drag operation.

For a complete example of how to add an event handler to an element using XAML, see How to: Handle a Routed Event.

Adding a handler for a routed event in an application that is created in code is straightforward. Routed event handlers can always be added through a helper method AddHandler (which is the same method that the existing backing calls for add.) However, existing WPF routed events generally have backing implementations of add and remove logic that allow the handlers for routed events to be added by a language-specific event syntax, which is more intuitive syntax than the helper method. The following is an example usage of the helper method:

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event     
 }

The next example shows the C# operator syntax (Visual Basic has slightly different operator syntax because of its handling of dereferencing):

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event     
}

For an example of how to add an event handler in code, see How to: Add an Event Handler Using Code.

If you are using Visual Basic, you can also use the Handles keyword to add handlers as part of the handler declarations. For more information, see Visual Basic and WPF Event Handling.

The Concept of Handled

All routed events share a common event data base class, RoutedEventArgsRoutedEventArgs defines the Handled property, which takes a Boolean value. The purpose of the Handled property is to enable any event handler along the route to mark the routed event as handled, by setting the value of Handled to true. After being processed by the handler at one element along the route, the shared event data is again reported to each listener along the route.

The value of Handled affects how a routed event is reported or processed as it travels further along the route. If Handled is true in the event data for a routed event, then handlers that listen for that routed event on other elements are generally no longer invoked for that particular event instance. This is true both for handlers attached in XAML and for handlers added by language-specific event handler attachment syntaxes such as += or Handles. For most common handler scenarios, marking an event as handled by setting Handled to true will "stop" routing for either a tunneling route or a bubbling route, and also for any event that is handled at a point in the route by a class handler.

However, there is a "handledEventsToo" mechanism whereby listeners can still run handlers in response to routed events where Handled is true in the event data. In other words, the event route is not truly stopped by marking the event data as handled. You can only use the handledEventsToo mechanism in code, or in an EventSetter:

In addition to the behavior that Handled state produces in routed events, the concept of Handled has implications for how you should design your application and write the event handler code. You can conceptualize Handled as being a simple protocol that is exposed by routed events. Exactly how you use this protocol is up to you, but the conceptual design for how the value of Handled is intended to be used is as follows:

  • If a routed event is marked as handled, then it does not need to be handled again by other elements along that route.

  • If a routed event is not marked as handled, then other listeners that were earlier along the route have chosen either not to register a handler, or the handlers that were registered chose not to manipulate the event data and set Handled to true. (Or, it is of course possible that the current listener is the first point in the route.) Handlers on the current listener now have three possible courses of action:

    • Take no action at all; the event remains unhandled, and the event routes to the next listener.

    • Execute code in response to the event, but make the determination that the action taken was not substantial enough to warrant marking the event as handled. The event routes to the next listener.

    • Execute code in response to the event. Mark the event as handled in the event data passed to the handler, because the action taken was deemed substantial enough to warrant marking as handled. The event still routes to the next listener, but with Handled=true in its event data, so only handledEventsToo listeners have the opportunity to invoke further handlers.

This conceptual design is reinforced by the routing behavior mentioned earlier: it is more difficult (although still possible in code or styles) to attach handlers for routed events that are invoked even if a previous handler along the route has already set Handled to true.

For more information about Handled, class handling of routed events, and recommendations about when it is appropriate to mark a routed event as Handled, see Marking Routed Events as Handled, and Class Handling.

In applications, it is quite common to just handle a bubbling routed event on the object that raised it, and not be concerned with the event's routing characteristics at all. However, it is still a good practice to mark the routed event as handled in the event data, to prevent unanticipated side effects just in case an element that is further up the element tree also has a handler attached for that same routed event.

Class Handlers

If you are defining a class that derives in some way from DependencyObject, you can also define and attach a class handler for a routed event that is a declared or inherited event member of your class. Class handlers are invoked before any instance listener handlers that are attached to an instance of that class, whenever a routed event reaches an element instance in its route.

Some WPF controls have inherent class handling for certain routed events. This might give the outward appearance that the routed event is not ever raised, but in reality it is being class handled, and the routed event can potentially still be handled by your instance handlers if you use certain techniques. Also, many base classes and controls expose virtual methods that can be used to override class handling behavior. For more information both on how to work around undesired class handling and on defining your own class handling in a custom class, see Marking Routed Events as Handled, and Class Handling.

Attached Events in WPF

The XAML language also defines a special type of event called an attached event. An attached event enables you to add a handler for a particular event to an arbitrary element. The element handling the event need not define or inherit the attached event, and neither the object potentially raising the event nor the destination handling instance must define or otherwise "own" that event as a class member.

The WPF input system uses attached events extensively. However, nearly all of these attached events are forwarded through base elements. The input events then appear as equivalent non-attached routed events that are members of the base element class. For instance, the underlying attached event Mouse.MouseDown can more easily be handled on any given UIElement by using MouseDown on that UIElement rather than dealing with attached event syntax either in XAML or code.

For more information about attached events in WPF, see Attached Events Overview.

Qualified Event Names in XAML

Another syntax usage that resembles typename.eventname attached event syntax but is not strictly speaking an attached event usage is when you attach handlers for routed events that are raised by child elements. You attach the handlers to a common parent, to take advantage of event routing, even though the common parent might not have the relevant routed event as a member. Consider this example again:

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

Here, the parent element listener where the handler is added is a StackPanel. However, it is adding a handler for a routed event that was declared and will be raised by the Button class (ButtonBase actually, but available to Button through inheritance). Button "owns" the event, but the routed event system permits handlers for any routed event to be attached to any UIElement or ContentElement instance listener that could otherwise attach listeners for a common language runtime (CLR) event. The default xmlns namespace for these qualified event attribute names is typically the default WPF xmlns namespace, but you can also specify prefixed namespaces for custom routed events. For more information about xmlns, see XAML Namespaces and Namespace Mapping for WPF XAML.

WPF Input Events

One frequent application of routed events within the WPF platform is for input events. In WPF, tunneling routed events names are prefixed with the word "Preview" by convention. Input events often come in pairs, with one being the bubbling event and the other being the tunneling event. For example, the KeyDown event and the PreviewKeyDown event have the same signature, with the former being the bubbling input event and the latter being the tunneling input event. Occasionally, input events only have a bubbling version, or perhaps only a direct routed version. In the documentation, routed event topics cross-reference similar routed events with alternative routing strategies if such routed events exist, and sections in the managed reference pages clarify the routing strategy of each routed event.

WPF input events that come in pairs are implemented so that a single user action from input, such as a mouse button press, will raise both routed events of the pair in sequence. First, the tunneling event is raised and travels its route. Then the bubbling event is raised and travels its route. The two events literally share the same event data instance, because the RaiseEvent method call in the implementing class that raises the bubbling event listens for the event data from the tunneling event and reuses it in the new raised event. Listeners with handlers for the tunneling event have the first opportunity to mark the routed event handled (class handlers first, then instance handlers). If an element along the tunneling route marked the routed event as handled, the already-handled event data is sent on for the bubbling event, and typical handlers attached for the equivalent bubbling input events will not be invoked. To outward appearances it will be as if the handled bubbling event has not even been raised. This handling behavior is useful for control compositing, where you might want all hit-test based input events or focus-based input events to be reported by your final control, rather than its composite parts. The final control element is closer to the root in the compositing, and therefore has the opportunity to class handle the tunneling event first and perhaps to "replace" that routed event with a more control-specific event, as part of the code that backs the control class.

As an illustration of how input event processing works, consider the following input event example. In the following tree illustration, leaf element #2 is the source of both a PreviewMouseDown and then a MouseDown event.

Event routing diagram

Input Event Bubbling and Tunneling

The order of event processing is as follows:

  1. PreviewMouseDown (tunnel) on root element.

  2. PreviewMouseDown (tunnel) on intermediate element #1.

  3. PreviewMouseDown (tunnel) on source element #2.

  4. MouseDown (bubble) on source element #2.

  5. MouseDown (bubble) on intermediate element #1.

  6. MouseDown (bubble) on root element.

A routed event handler delegate provides references to two objects: the object that raised the event and the object where the handler was invoked. The object where the handler was invoked is the object reported by the sender parameter. The object where the event was first raised is reported by the Source property in the event data. A routed event can still be raised and handled by the same object, in which case sender and Source are identical (this is the case with Steps 3 and 4 in the event processing example list).

Because of tunneling and bubbling, parent elements receive input events where the Source is one of their child elements. When it is important to know what the source element is, you can identify the source element by accessing the Source property.

Usually, once the input event is marked Handled, further handlers are not invoked. Typically, you should mark input events as handled as soon as a handler is invoked that addresses your application-specific logical handling of the meaning of the input event.

The exception to this general statement about Handled state is that input event handlers that are registered to deliberately ignore Handled state of the event data would still be invoked along either route. For more information, see Preview Events or Marking Routed Events as Handled, and Class Handling.

The shared event data model between tunneling and bubbling events, and the sequential raising of first tunneling then bubbling events, is not a concept that is generally true for all routed events. That behavior is specifically implemented by how WPF input devices choose to raise and connect the input event pairs. Implementing your own input events is an advanced scenario, but you might choose to follow that model for your own input events also.

Certain classes choose to class-handle certain input events, usually with the intent of redefining what a particular user-driven input event means within that control and raising a new event. For more information, see Marking Routed Events as Handled, and Class Handling.

For more information on input and how input and events interact in typical application scenarios, see Input Overview.

EventSetters and EventTriggers

In styles, you can include some pre-declared XAML event handling syntax in the markup by using an EventSetter. When the style is applied, the referenced handler is added to the styled instance. You can declare an EventSetter only for a routed event. The following is an example. Note that the b1SetColor method referenced here is in a code-behind file.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

The advantage gained here is that the style is likely to contain a great deal of other information that could apply to any button in your application, and having the EventSetter be part of that style promotes code reuse even at the markup level. Also, an EventSetter abstracts method names for handlers one step further away from the general application and page markup.

Another specialized syntax that combines the routed event and animation features of WPF is an EventTrigger. As with EventSetter, only routed events may be used for an EventTrigger. Typically, an EventTrigger is declared as part of a style, but an EventTrigger can also be declared on page-level elements as part of the Triggers collection, or in a ControlTemplate. An EventTrigger enables you to specify a Storyboard that runs whenever a routed event reaches an element in its route that declares an EventTrigger for that event. The advantage of an EventTrigger over just handling the event and causing it to start an existing storyboard is that an EventTrigger provides better control over the storyboard and its run-time behavior. For more information, see How to: Use Event Triggers to Control a Storyboard After It Starts.

More About Routed Events

This topic mainly discusses routed events from the perspective of describing the basic concepts and offering guidance on how and when to respond to the routed events that are already present in the various base elements and controls. However, you can create your own routed event on your custom class along with all the necessary support, such as specialized event data classes and delegates. The routed event owner can be any class, but routed events must be raised by and handled by UIElement or ContentElement derived classes in order to be useful. For more information about custom events, see How to: Create a Custom Routed Event.       




Attached Events Overview


Extensible Application Markup Language (XAML) defines a language component and type of event called an attached event. The concept of an attached event enables you to add a handler for a particular event to an arbitrary element rather than to an element that actually defines or inherits the event. In this case, neither the object potentially raising the event nor the destination handling instance defines or otherwise "owns" the event. 

Prerequisites

This topic assumes that you have read Routed Events Overview and XAML Overview (WPF).

Attached Event Syntax

Attached events have a XAML syntax and a coding pattern that must be used by the backing code in order to support the attached event usage.

In XAML syntax, the attached event is specified not just by its event name, but by its owning type plus the event name, separated by a dot (.). Because the event name is qualified with the name of its owning type, the attached event syntax allows any attached event to be attached to any element that can be instantiated.

For example, the following is the XAML syntax for attaching a handler for a custom NeedsCleaning attached event:

<aqua:Aquarium Name="theAquarium" Height="600" Width="800" aqua:AquariumFilter.NeedsCleaning="WashMe"/>

Note the aqua: prefix; the prefix is necessary in this case because the attached event is a custom event that comes from a custom mapped xmlns.

How WPF Implements Attached Events

In WPF, attached events are backed by a RoutedEvent field and are routed through the tree after they are raised. Typically, the source of the attached event (the object that raises the event) is a system or service source, and the object that runs the code that raises the event is therefore not a direct part of the element tree.

Scenarios for Attached Events

In WPF, attached events are present in certain feature areas where there is service-level abstraction, such as for the events enabled by the static Mouse class or the Validation class. Classes that interact with or use the service can either use the event in the attached event syntax, or they can choose to surface the attached event as a routed event that is part of how the class integrates the capabilities of the service.

Although WPF defines a number of attached events, the scenarios where you will either use or handle the attached event directly are very limited. Generally, the attached event serves an architecture purpose, but is then forwarded to a non-attached (backed with a CLR event "wrapper") routed event.

For instance, the underlying attached event Mouse.MouseDown can more easily be handled on any given UIElement by using MouseDown on that UIElement rather than dealing with attached event syntax either in XAML or code. The attached event serves a purpose in the architecture because it allows for future expansion of input devices. The hypothetical device would only need to raise Mouse.MouseDown in order to simulate mouse input, and would not need to derive from Mouse to do so. However, this scenario involves code handling of the events, and XAML handling of the attached event is not relevant to this scenario.

Handling an Attached Event in WPF

The process for handling an attached event, and the handler code that you will write, is basically the same as for a routed event.

In general, a WPF attached event is not very different from a WPF routed event. The differences are how the event is sourced and how it is exposed by a class as a member (which also affects the XAML handler syntax).

However, as noted earlier, the existing WPF attached events are not particularly intended for handling in WPF. More often, the purpose of the event is to enable a composited element to report a state to a parent element in compositing, in which case the event is usually raised in code and also relies on class handling in the relevant parent class. For instance, items within a Selector are expected to raise the attached Selected event, which is then class handled by the Selector class and then potentially converted by the Selector class into a different routed event, SelectionChanged. For more information on routed events and class handling, see Marking Routed Events as Handled, and Class Handling.

Defining Your Own Attached Events as Routed Events

If you are deriving from common WPF base classes, you can implement your own attached events by including certain pattern methods in your class and by using utility methods that are already present on the base classes.

The pattern is as follows:

  • A method Add*Handler with two parameters. The first parameter must identify the event, and the identified event must match names with the * in the method name. The second parameter is the handler to add. The method must be public and static, with no return value.

  • A method Remove*Handler with two parameters. The first parameter must identify the event, and the identified event must match names with the * in the method name. The second parameter is the handler to remove. The method must be public and static, with no return value.

The Add*Handler accessor method facilitates the XAML processing when attached event handler attributes are declared on an element. The Add*Handler and Remove*Handler methods also enable code access to the event handler store for the attached event.

This general pattern is not yet precise enough for practical implementation in a framework, because any given XAML reader implementation might have different schemes for identifying underlying events in the supporting language and architecture. This is one of the reasons that WPF implements attached events as routed events; the identifier to use for an event (RoutedEvent) is already defined by the WPF event system. Also, routing an event is a natural implementation extension on the XAML language-level concept of an attached event.

The Add*Handler implementation for a WPF attached event consists of calling the AddHandler with the routed event and handler as arguments.

This implementation strategy and the routed event system in general restrict handling for attached events to either UIElement derived classes or ContentElement derived classes, because only those classes have AddHandler implementations.

For example, the following code defines the NeedsCleaning attached event on the owner class Aquarium, using the WPF attached event strategy of declaring the attached event as a routed event.

public static readonly RoutedEvent NeedsCleaningEvent = EventManager.RegisterRoutedEvent("NeedsCleaning", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AquariumFilter));
public static void AddNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
    UIElement uie = d as UIElement;
    if (uie != null)
    {
        uie.AddHandler(AquariumFilter.NeedsCleaningEvent, handler);
    }
}
public static void RemoveNeedsCleaningHandler(DependencyObject d, RoutedEventHandler handler)
{
    UIElement uie = d as UIElement;
    if (uie != null)
    {
        uie.RemoveHandler(AquariumFilter.NeedsCleaningEvent, handler);
    }
}

Note that the method used to establish the attached event identifier field, RegisterRoutedEvent, is actually the same method that is used to register a non-attached routed event. Attached events and routed events all are registered to a centralized internal store. This event store implementation enables the "events as an interface" conceptual consideration that is discussed in Routed Events Overview.

Raising a WPF Attached Event

You do not typically need to raise existing WPF defined attached events from your code. These events follow the general "service" conceptual model, and service classes such as InputManager are responsible for raising the events.

However, if you are defining a custom attached event based on the WPF model of basing attached events on RoutedEvent, you can use RaiseEvent to raise an attached event from any UIElement or ContentElement. Raising a routed event (attached or not) requires that you declare a particular element in the element tree as the event source; that source is reported as the RaiseEvent caller. Determining which element is reported as the source in the tree is your service's responsibility




Object Lifetime Events


This topic describes the specific WPF events that signify stages in an object lifetime of creation, use, and destruction.

Prerequisites

This topic assumes that you understand dependency properties from the perspective of a consumer of existing dependency properties on Windows Presentation Foundation (WPF) classes, and have read the Dependency Properties Overview topic. In order to follow the examples in this topic, you should also understand Extensible Application Markup Language (XAML) (see XAML Overview (WPF)) and know how to write WPF applications.

Object Lifetime Events

All objects in Microsoft .NET Framework managed code go through a similar set of stages of life, creation, use, and destruction. Many objects also have a finalization stage of life that occurs as part of the destruction phase. WPF objects, more specifically the visual objects that WPF identifies as elements, also have a set of common stages of object life. The WPF programming and application models expose these stages as a series of events. There are four main types of objects in WPF with respect to lifetime events; elements in general, window elements, navigation hosts, and application objects. Windows and navigation hosts are also within the larger grouping of visual objects (elements). This topic describes the lifetime events that are common to all elements and then introduces the more specific ones that apply to application definitions, windows or navigation hosts.

Common Lifetime Events for Elements

Any WPF framework-level element (those objects deriving from either FrameworkElement or FrameworkContentElement) has three common lifetime events: InitializedLoaded, and Unloaded.

Initialized

Initialized is raised first, and roughly corresponds to the initialization of the object by the call to its constructor. Because the event happens in response to initialization, you are guaranteed that all properties of the object are set. (An exception is expression usages such as dynamic resources or binding; these will be unevaluated expressions.) As a consequence of the requirement that all properties are set, the sequence of Initialized being raised by nested elements that are defined in markup appears to occur in order of deepest elements in the element tree first, then parent elements toward the root. This order is because the parent-child relationships and containment are properties, and therefore the parent cannot report initialization until the child elements that fill the property are also completely initialized.

When you are writing handlers in response to the Initialized event, you must consider that there is no guarantee that all other elements in the element tree (either logical tree or visual tree) around where the handler is attached have been created, particularly parent elements. Member variables may be null, or data sources might not yet be populated by the underlying binding (even at the expression level).

Loaded

Loaded is raised next. The Loaded event is raised before the final rendering, but after the layout system has calculated all necessary values for rendering. Loaded entails that the logical tree that an element is contained within is complete, and connects to a presentation source that provides the HWND and the rendering surface. Standard data binding (binding to local sources, such as other properties or directly defined data sources) will have occurred prior to Loaded. Asynchronous data binding (external or dynamic sources) might have occurred, but by definition of its asynchronous nature cannot be guaranteed to have occurred.

The mechanism by which the Loaded event is raised is different than Initialized. The Initialized event is raised element by element, without a direct coordination by a completed element tree. By contrast, the Loaded event is raised as a coordinated effort throughout the entire element tree (specifically, the logical tree). When all elements in the tree are in a state where they are considered loaded, the Loaded event is first raised on the root element. The Loaded event is then raised successively on each child element.

System_CAPS_noteNote

This behavior might superficially resemble tunneling for a routed event. However, no information is carried from event to event. Each element always has the opportunity to handle its Loaded event, and marking the event data as handled has no effect beyond that element.

Unloaded

Unloaded is raised last and is initiated by either the presentation source or the visual parent being removed. When Unloaded is raised and handled, the element that is the event source parent (as determined by Parent property) or any given element upwards in the logical or visual trees may have already been unset, meaning that data binding, resource references, and styles may not be set to their normal or last known run-time value.

Lifetime Events Application Model Elements

Building on the common lifetime events for elements are the following application model elements: ApplicationWindowPageNavigationWindow, and Frame. These extend the common lifetime events with additional events that are relevant to their specific purpose. These are discussed in detail in the following locations:




Marking Routed Events as Handled, and Class Handling


Handlers for a routed event can mark the event handled within the event data. Handling the event will effectively shorten the route. Class handling is a programming concept that is supported by routed events. A class handler has the opportunity to handle a particular routed event at a class level with a handler that is invoked before any instance handler on any instance of the class.

Prerequisites

This topic elaborates on concepts introduced in the Routed Events Overview.

When to Mark Events as Handled

When you set the value of the Handled property to true in the event data for a routed event, this is referred to as "marking the event handled". There is no absolute rule for when you should mark routed events as handled, either as an application author, or as a control author who responds to existing routed events or implements new routed events. For the most part, the concept of "handled" as carried in the routed event's event data should be used as a limited protocol for your own application's responses to the various routed events exposed in WPF APIs as well as for any custom routed events. Another way to consider the "handled" issue is that you should generally mark a routed event handled if your code responded to the routed event in a significant and relatively complete way. Typically, there should not be more than one significant response that requires separate handler implementations for any single routed event occurrence. If more responses are needed, then the necessary code should be implemented through application logic that is chained within a single handler rather than by using the routed event system for forwarding. The concept of what is "significant" is also subjective, and depends on your application or code. As general guidance, some "significant response" examples include: setting focus, modifying public state, setting properties that affect the visual representation, and raising other new events. Examples of nonsignificant responses include: modifying private state (with no visual impact, or programmatic representation), logging of events, or looking at arguments of an event and choosing not to respond to it.

The routed event system behavior reinforces this "significant response" model for using handled state of a routed event, because handlers added in XAML or the common signature of AddHandler are not invoked in response to a routed event where the event data is already marked handled. You must go through the extra effort of adding a handler with the handledEventsToo parameter version (AddHandler(RoutedEvent, Delegate, Boolean)) in order to handle routed events that are marked handled by earlier participants in the event route.

In some circumstances, controls themselves mark certain routed events as handled. A handled routed event represents a decision by WPF control authors that the control's actions in response to the routed event are significant or complete as part of the control implementation, and the event needs no further handling. Usually this is done by adding a class handler for an event, or by overriding one of the class handler virtuals that exist on a base class. You can still work around this event handling if necessary; see Working Around Event Suppression by Controls later in this topic.

"Preview" (Tunneling) Events vs. Bubbling Events, and Event Handling

Preview routed events are events that follow a tunneling route through the element tree. The "Preview" expressed in the naming convention is indicative of the general principle for input events that preview (tunneling) routed events are raised prior to the equivalent bubbling routed event. Also, input routed events that have a tunneling and bubbling pair have a distinct handling logic. If the tunneling/preview routed event is marked as handled by an event listener, then the bubbling routed event will be marked handled even before any listeners of the bubbling routed event receive it. The tunneling and bubbling routed events are technically separate events, but they deliberately share the same instance of event data to enable this behavior.

The connection between the tunneling and bubbling routed events is accomplished by the internal implementation of how any given WPF class raises its own declared routed events, and this is true of the paired input routed events. But unless this class-level implementation exists, there is no connection between a tunneling routed event and a bubbling routed event that share the naming scheme: without such implementation they would be two entirely separate routed events and would not be raised in sequence or share event data.

For more information about how to implement tunnel/bubble input routed event pairs in a custom class, see How to: Create a Custom Routed Event.

Class Handlers and Instance Handlers

Routed events consider two different types of listeners to the event: class listeners and instance listeners. Class listeners exist because types have called a particular EventManager API ,RegisterClassHandler, in their static constructor, or have overridden a class handler virtual method from an element base class. Instance listeners are particular class instances/elements where one or more handlers have been attached for that routed event by a call to AddHandler. Existing WPF routed events make calls to AddHandler as part of the common language runtime (CLR) event wrapper add{} and remove{} implementations of the event, which is also how the simple XAML mechanism of attaching event handlers via an attribute syntax is enabled. Therefore even the simple XAML usage ultimately equates to an AddHandler call.

Elements within the visual tree are checked for registered handler implementations. Handlers are potentially invoked throughout the route, in the order that is inherent in the type of the routing strategy for that routed event. For instance, bubbling routed events will first invoke those handlers that are attached to the same element that raised the routed event. Then the routed event "bubbles" to the next parent element and so on until the application root element is reached.

From the perspective of the root element in a bubbling route, if class handling or any element closer to the source of the routed event invoke handlers that mark the event arguments as being handled, then handlers on the root elements are not invoked, and the event route is effectively shortened before reaching that root element. However, the route is not completely halted, because handlers can be added using a special conditional that they should still be invoked, even if a class handler or instance handler has marked the routed event as handled. This is explained in Adding Instance Handlers That Are Raised Even When Events Are Marked Handled, later in this topic.

At a deeper level than the event route, there are also potentially multiple class handlers acting on any given instance of a class. This is because the class handling model for routed events enables all possible classes in a class hierarchy to each register its own class handler for each routed event. Each class handler is added to an internal store, and when the event route for an application is constructed, the class handlers are all added to the event route. Class handlers are added to the route such that the most-derived class handler is invoked first, and class handlers from each successive base class are invoked next. Generally, class handlers are not registered such that they also respond to routed events that were already marked handled. Therefore, this class handling mechanism enables one of two choices:

  • Derived classes can supplement the class handling that is inherited from the base class by adding a handler that does not mark the routed event handled, because the base class handler will be invoked sometime after the derived class handler.

  • Derived classes can replace the class handling from the base class by adding a class handler that marks the routed event handled. You should be cautious with this approach, because it will potentially change the intended base control design in areas such as visual appearance, state logic, input handling, and command handling.

Class Handling of Routed Events by Control Base Classes

On each given element node in an event route, class listeners have the opportunity to respond to the routed event before any instance listener on the element can. For this reason, class handlers are sometimes used to suppress routed events that a particular control class implementation does not wish to propagate further, or to provide special handling of that routed event that is a feature of the class. For instance, a class might raise its own class-specific event that contains more specifics about what some user input condition means in the context of that particular class. The class implementation might then mark the more general routed event as handled. Class handlers are typically added such that they are not invoked for routed events where shared event data was already marked handled, but for atypical cases there is also a RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) signature that registers class handlers to invoke even when routed events are marked handled.

Class Handler Virtuals

Some elements, particularly the base elements such as UIElement, expose empty "On*Event" and "OnPreview*Event" virtual methods that correspond to their list of public routed events. These virtual methods can be overridden to implement a class handler for that routed event. The base element classes register these virtual methods as their class handler for each such routed event using RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) as described earlier. The On*Event virtual methods make it much simpler to implement class handling for the relevant routed events, without requiring special initialization in static constructors for each type. For instance, you can add class handling for the DragEnter event in any UIElement derived class by overriding the OnDragEnter virtual method. Within the override, you could handle the routed event, raise other events, initiate class-specific logic that might change element properties on instances, or any combination of those actions. You should generally call the base implementation in such overrides even if you mark the event handled. Calling the base implementation is strongly recommended because the virtual method is on the base class. The standard protected virtual pattern of calling the base implementations from each virtual essentially replaces and parallels a similar mechanism that is native to routed event class handling, whereby class handlers for all classes in a class hierarchy are called on any given instance, starting with the most-derived class' handler and continuing to the base class handler. You should only omit the base implementation call if your class has a deliberate requirement to change the base class handling logic. Whether you call the base implementation before or after your overriding code will depend on the nature of your implementation.

Input Event Class Handling

The class handler virtual methods are all registered such that they are only invoked in cases where any shared event data are not already marked handled. Also, for the input events uniquely, the tunneling and bubbling versions typically are raised in sequence and share event data. This entails that for a given pair of class handlers of input events where one is the tunneling version and the other is the bubbling version, you may not want to mark the event handled immediately. If you implement the tunneling class handling virtual method to mark the event handled, that will prevent the bubbling class handler from being invoked (as well as preventing any normally registered instance handlers for either the tunneling or bubbling event from being invoked).

Once class handling on a node is complete, the instance listeners are considered.

Adding Instance Handlers That Are Raised Even When Events Are Marked Handled

The AddHandler method supplies a particular overload that allows you to add handlers that will be invoked by the event system whenever an event reaches the handling element in the route, even if some other handler has already adjusted the event data to mark that event as handled. This is not typically done. Generally, handlers can be written to adjust all areas of application code that might be influenced by an event, regardless of where it was handled in an element tree, even if multiple end results are desired. Also, typically there is really only one element that needs to respond to that event, and the appropriate application logic had already happened. But the handledEventsToo overload is available for the exceptional cases where some other element in an element tree or control compositing has already marked an event as handled, but other elements either higher or lower in the element tree (depending on route) still wish to have their own handlers invoked.

When to Mark Handled Events as Unhandled

Generally, routed events that are marked handled should not be marked unhandled (Handled set back to false) even by handlers that act on handledEventsToo. However, some input events have high-level and lower-level event representations that can overlap when the high-level event is seen at one position in the tree and the low-level event at another position. For instance, consider the case where a child element listens to a high-level key event such as TextInput while a parent element listens to a low-level event such as KeyDown. If the parent element handles the low-level event, the higher-level event can be suppressed even in the child element that intuitively should have first opportunity to handle the event.

In these situations it may be necessary to add handlers to both parent elements and child elements for the low-level event. The child element handler implementation can mark the low-level event as handled, but the parent element handler implementation would set it unhandled again so that further elements up the tree (as well as the high-level event) can have the opportunity to respond. This situation is should be fairly rare.

Deliberately Suppressing Input Events for Control Compositing

The main scenario where class handling of routed events is used is for input events and composited controls. A composited control is by definition composed of multiple practical controls or control base classes. Often the author of the control wishes to amalgamate all of the possible input events that each of the subcomponents might raise, in order to report the entire control as the singular event source. In some cases the control author might wish to suppress the events from components entirely, or substitute a component-defined event that carries more information or implies a more specific behavior. The canonical example that is immediately visible to any component author is how a Windows Presentation Foundation (WPF) Button handles any mouse event that will eventually resolve to the intuitive event that all buttons have: a Click event.

The Button base class (ButtonBase) derives from Control which in turn derives from FrameworkElement and UIElement, and much of the event infrastructure needed for control input processing is available at the UIElement level. In particular, UIElement processes general Mouse events that handle hit testing for the mouse cursor within its bounds, and provides distinct events for the most common button actions, such as MouseLeftButtonDownUIElement also provides an empty virtual OnMouseLeftButtonDown as the preregistered class handler for MouseLeftButtonDown, and ButtonBase overrides it. Similarly, ButtonBase uses class handlers for MouseLeftButtonUp. In the overrides, which are passed the event data, the implementations mark that RoutedEventArgs instance as handled by setting Handled to true, and that same event data is what continues along the remainder of the route to other class handlers and also to instance handlers or event setters. Also, the OnMouseLeftButtonUp override will next raise the Click event. The end result for most listeners will be that the MouseLeftButtonDown and MouseLeftButtonUp events "disappear" and are replaced instead by Click, an event that holds more meaning because it is known that this event originated from a true button and not some composite piece of the button or from some other element entirely.

Working Around Event Suppression by Controls

Sometimes this event suppression behavior within individual controls can interfere with some more general intentions of event handling logic for your application. For instance, if for some reason your application had a handler for MouseLeftButtonDown located at the application root element, you would notice that any mouse click on a button would not invoke MouseLeftButtonDown or MouseLeftButtonUp handlers at the root level. The event itself actually did bubble up (again, event routes are not truly ended, but the routed event system changes their handler invocation behavior after being marked handled). When the routed event reached the button, the ButtonBase class handling marked the MouseLeftButtonDown handled because it wished to substitute the Click event with more meaning. Therefore, any standard MouseLeftButtonDown handler further up the route would not be invoked. There are two techniques you can use to ensure that your handlers would be invoked in this circumstance.

The first technique is to deliberately add the handler using the handledEventsToo signature of AddHandler(RoutedEvent, Delegate, Boolean). A limitation of this approach is that this technique for attaching an event handler is only possible from code, not from markup. The simple syntax of specifying the event handler name as an event attribute value via Extensible Application Markup Language (XAML) does not enable that behavior.

The second technique works only for input events, where the tunneling and bubbling versions of the routed event are paired. For these routed events, you can add handlers to the preview/tunneling equivalent routed event instead. That routed event will tunnel through the route starting from the root, so the button class handling code would not intercept it, presuming that you attached the Preview handler at some ancestor element level in the application's element tree. If you use this approach, be cautious about marking any Preview event handled. For the example given with PreviewMouseLeftButtonDown being handled at the root element, if you marked the event as Handled in the handler implementation, you would actually suppress the Click event. That is typically not desirable behavior.




Preview Events


Preview events, also known as tunneling events, are routed events where the direction of the route travels from the application root towards the element that raised the event and is reported as the source in event data. Not all event scenarios support or require preview events; this topic describes the situations where preview events exist, how applications or components should handle them, and cases where creating preview events in custom components or classes might be appropriate.

Preview Events and Input

When you handle Preview events in general, be cautious about marking the events handled in the event data. Handling a Preview event on any element other than the element that raised it (the element that is reported as the source in the event data) has the effect of not providing an element the opportunity to handle the event that it originated. Sometimes this is the desired result, particularly if the elements in question exist in relationships within the compositing of a control.

For input events specifically, Preview events also share event data instances with the equivalent bubbling event. If you use a Preview event class handler to mark the input event handled, the bubbling input event class handler will not be invoked. Or, if you use a Preview event instance handler to mark the event handled, handlers for the bubbling event will not typically be invoked. Class handlers or instance handlers can be registered or attached with an option to be invoked even if the event is marked handled, but that technique is not commonly used.

For more information about class handling and how it relates to Preview events see Marking Routed Events as Handled, and Class Handling.

Working Around Event Suppression by Controls

One scenario where Preview events are commonly used is for composited control handling of input events. Sometimes, the author of the control suppresses a certain event from originating from their control, perhaps in order to substitute a component-defined event that carries more information or implies a more specific behavior. For instance, a Windows Presentation Foundation (WPF) Buttonsuppresses MouseLeftButtonDown and MouseLeftButtonDown bubbling events raised by the Button or its composite elements in favor of capturing the mouse and raising a Click event that is always raised by the Button itself. The event and its data still continue along the route, but because the Button marks the event data as Handled, only handlers for the event that specifically indicated they should act in the handledEventsToo case are invoked. If other elements towards the root of your application still wanted an opportunity to handle a control-suppressed event, one alternative is to attach handlers in code with handledEventsToo specified as true. But often a simpler technique is to change the routing direction you handle to be the Preview equivalent of an input event. For instance, if a control suppresses MouseLeftButtonDown, try attaching a handler for PreviewMouseLeftButtonDown instead. This technique only works for base element input events such as MouseLeftButtonDown. These input events use tunnel/bubble pairs, raise both events, and share the event data.

Each of these techniques has either side effects or limitations. The side effect of handling the Preview event is that handling the event at that point might disable handlers that expect to handle the bubbling event, and therefore the limitation is that it is usually not a good idea to mark the event handled while it is still on the Preview part of the route. The limitation of the handledEventsToo technique is that you cannot specify a handledEventsToo handler in XAML as an attribute, you must register the event handler in code after obtaining an object reference to the element where the handler is to be attached.




Property Change Events


Windows Presentation Foundation (WPF) defines several events that are raised in response to a change in the value of a property. Often the property is a dependency property. The event itself is sometimes a routed event and is sometimes a standard common language runtime (CLR) event. The definition of the event varies depending on the scenario, because some property changes are more appropriately routed through an element tree, whereas other property changes are generally only of concern to the object where the property changed.

Identifying a Property Change Event

Not all events that report a property change are explicitly identified as a property changed event, either by virtue of a signature pattern or a naming pattern. Generally, the description of the event in the software development kit (SDK) documentation indicates whether the event is directly tied to a property value change and provides cross-references between the property and event.

RoutedPropertyChanged Events

Certain events use an event data type and delegate that are explicitly used for property change events. The event data type is RoutedPropertyChangedEventArgs<T>, and the delegate is RoutedPropertyChangedEventHandler<T>. The event data and delegate both have a generic type parameter that is used to specify the actual type of the changing property when you define the handler. The event data contains two properties, OldValue and NewValue, which are both then passed as the type argument in the event data.

The "Routed" part of the name indicates that the property changed event is registered as a routed event. The advantage of routing a property changed event is that the top level of a control can receive property changed events if properties on the child elements (the control's composite parts) change values. For instance, you might create a control that incorporates a RangeBase control such as a Slider. If the value of the Valueproperty changes on the slider part, you might want to handle that change on the parent control rather than on the part.

Because you have an old value and a new value, it might be tempting to use this event handler as a validator for the property value. However, that is not the design intention of most property changed events. Generally, the values are provided so that you can act on those values in other logic areas of your code, but actually changing the values from within the event handler is not advisable, and may cause unintentional recursion depending on how your handler is implemented.

If your property is a custom dependency property, or if you are working with a derived class where you have defined the instantiation code, there is a much better mechanism for tracking property changes that is built in to the WPF property system: the property system callbacks CoerceValueCallback and PropertyChangedCallback. For more details about how you can use the WPF property system for validation and coercion, see Dependency Property Callbacks and Validation and Custom Dependency Properties.

DependencyPropertyChanged Events

Another pair of types that are part of a property changed event scenario is DependencyPropertyChangedEventArgs and DependencyPropertyChangedEventHandler. Events for these property changes are not routed; they are standard CLR events. DependencyPropertyChangedEventArgs is an unusual event data reporting type because it does not derive from EventArgsDependencyPropertyChangedEventArgs is a structure, not a class. 

Events that use DependencyPropertyChangedEventArgs and DependencyPropertyChangedEventHandler are slightly more common than RoutedPropertyChanged events. An example of an event that uses these types is IsMouseCapturedChanged.

Like RoutedPropertyChangedEventArgs<T>DependencyPropertyChangedEventArgs also reports an old and new value for the property. Also, the same considerations about what you can do with the values apply; it is generally not recommended that you attempt to change the values again on the sender in response to the event.

Property Triggers

A closely related concept to a property changed event is a property trigger. A property trigger is created within a style or template and enables you to create a conditional behavior based on the value of the property where the property trigger is assigned.

The property for a property trigger must be a dependency property. It can be (and frequently is) a read-only dependency property. A good indicator for when a dependency property exposed by a control is at least partially designed to be a property trigger is if the property name begins with "Is". Properties that have this naming are often a read-only Boolean dependency property where the primary scenario for the property is reporting control state that might have consequences to the real-time UI and is thus a property trigger candidate.

Some of these properties also have a dedicated property changed event. For instance, the property IsMouseCaptured has a property changed event IsMouseCapturedChanged. The property itself is read-only, with its value adjusted by the input system, and the input system raises IsMouseCapturedChanged on each real-time change.

Compared to a true property changed event, using a property trigger to act on a property change has some limitations.

Property triggers work through an exact match logic. You specify a property and a value that indicates the specific value for which the trigger will act. For instance: <Setter Property="IsMouseCaptured" Value="true"> ... </Setter>. Because of this limitation, the majority of property trigger usages will be for Boolean properties, or properties that take a dedicated enumeration value, where the possible value range is manageable enough to define a trigger for each case. Or property triggers might exist only for special values, such as when an items count reaches zero, and there would be no trigger that accounts for the cases when the property value changes away from zero again (instead of triggers for all cases, you might need a code event handler here, or a default behavior that toggles back from the trigger state again when the value is nonzero).

The property trigger syntax is analogous to an "if" statement in programming. If the trigger condition is true, then the "body" of the property trigger is "executed". The "body" of a property trigger is not code, it is markup. That markup is limited to using one or more Setter elements to set other properties of the object where the style or template is being applied.

To offset the "if" condition of a property trigger that has a wide variety of possible values, it is generally advisable to set that same property value to a default by using a Setter. This way, the Trigger contained setter will have precedence when the trigger condition is true, and the Setter that is not within a Trigger will have precedence whenever the trigger condition is false.

Property triggers are generally appropriate for scenarios where one or more appearance properties should change, based on the state of another property on the same element.

To learn more about property triggers, see Styling and Templating.




Visual Basic and WPF Event Handling


For the Microsoft Visual Basic .NET language specifically, you can use the language-specific Handles keyword to associate event handlers with instances, instead of attaching event handlers with attributes or using the AddHandler method. However, the Handles technique for attaching handlers to instances does have some limitations, because the Handles syntax cannot support some of the specific routed event features of the WPF event system.

Using "Handles" in a WPF Application

The event handlers that are connected to instances and events with Handles must all be defined within the partial class declaration of the instance, which is also a requirement for event handlers that are assigned through attribute values on elements. You can only specify Handles for an element on the page that has a Name property value (or x:Name Directive declared). This is because the Name in XAML creates the instance reference that is necessary to support the Instance.Event reference format required by the Handles syntax. The only element that can be used for Handles without a Name reference is the root-element instance that defines the partial class.

You can assign the same handler to multiple elements by separating Instance.Event references after Handles with commas.

You can use Handles to assign more than one handler to the same Instance.Event reference. Do not assign any importance to the order in which handlers are given in the Handles reference; you should assume that handlers that handle the same event can be invoked in any order.

To remove a handler that was added with Handles in the declaration, you can call RemoveHandler.

You can use Handles to attach handlers for routed events, so long as you attach handlers to instances that define the event being handled in their members tables. For routed events, handlers that are attached with Handles follow the same routing rules as do handlers that are attached as XAML attributes, or with the common signature of AddHandler. This means that if the event is already marked handled (the Handled property in the event data is True), then handlers attached with Handles are not invoked in response to that event instance. The event could be marked handled by instance handlers on another element in the route, or by class handling either on the current element or earlier elements along the route. For input events that support paired tunnel/bubble events, the tunneling route may have marked the event pair handled. For more information about routed events, see Routed Events Overview.

Limitations of "Handles" for Adding Handlers

Handles cannot reference handlers for attached events. You must use the add accessor method for that attached event, or typename.eventnameevent attributes in XAML. For details, see Routed Events Overview.

For routed events, you can only use Handles to assign handlers for instances where that event exists in the instance members table. However, with routed events in general, a parent element can be a listener for an event from child elements, even if the parent element does not have that event in its members table. In attribute syntax, you can specify this through a typename.membername attribute form that qualifies which type actually defines the event you want to handle. For instance, a parent Page (with no Click event defined) can listen for button-click events by assigning an attribute handler in the form Button.Click. But Handles does not support the typename.membername form, because it must support a conflicting Instance.Event form. For details, see Routed Events Overview.

Handles cannot attach handlers that are invoked for events that are already marked handled. Instead, you must use code and call the handledEventsToo overload of AddHandler(RoutedEvent, Delegate, Boolean).

System_CAPS_noteNote

Do not use the Handles syntax in Visual Basic code when you specify an event handler for the same event in XAML. In this case, the event handler is called twice.

How WPF Implements "Handles" Functionality

When a Extensible Application Markup Language (XAML) page is compiled, the intermediate file declares Friend WithEvents references to every element on the page that has a Name property set (or x:Name Directive declared). Each named instance is potentially an element that can be assigned to a handler through Handles.

System_CAPS_noteNote

Within Microsoft Visual Studio, IntelliSense can show you completion for which elements are available for a Handles reference in a page. However, this might take one compile pass so that the intermediate file can populate all the Friends references.




Weak Event Patterns


In applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the weak event pattern.

Why Implement the Weak Event Pattern?

Listening for events can lead to memory leaks. The typical technique for listening to an event is to use the language-specific syntax that attaches a handler to an event on a source. For example, in C#, that syntax is: source.SomeEvent += new SomeEventHandler(MyEventHandler).

This technique creates a strong reference from the event source to the event listener. Ordinarily, attaching an event handler for a listener causes the listener to have an object lifetime that is influenced by the object lifetime of the source (unless the event handler is explicitly removed). But in certain circumstances, you might want the object lifetime of the listener to be controlled by other factors, such as whether it currently belongs to the visual tree of the application, and not by the lifetime of the source. Whenever the source object lifetime extends beyond the object lifetime of the listener, the normal event pattern leads to a memory leak: the listener is kept alive longer than intended.

The weak event pattern is designed to solve this memory leak problem. The weak event pattern can be used whenever a listener needs to register for an event, but the listener does not explicitly know when to unregister. The weak event pattern can also be used whenever the object lifetime of the source exceeds the useful object lifetime of the listener. (In this case, useful is determined by you.) The weak event pattern allows the listener to register for and receive the event without affecting the object lifetime characteristics of the listener in any way. In effect, the implied reference from the source does not determine whether the listener is eligible for garbage collection. The reference is a weak reference, thus the naming of the weak event pattern and the related APIs. The listener can be garbage collected or otherwise destroyed, and the source can continue without retaining noncollectible handler references to a now destroyed object.

Who Should Implement the Weak Event Pattern?

Implementing the weak event pattern is interesting primarily for control authors. As a control author, you are largely responsible for the behavior and containment of your control and the impact it has on applications in which it is inserted. This includes the control object lifetime behavior, in particular the handling of the described memory leak problem.

Certain scenarios inherently lend themselves to the application of the weak event pattern. One such scenario is data binding. In data binding, it is common for the source object to be completely independent of the listener object, which is a target of a binding. Many aspects of WPF data binding already have the weak event pattern applied in how the events are implemented.

How to Implement the Weak Event Pattern

There are three ways to implement weak event pattern. The following table lists the three approaches and provides some guidance for when you should use each.

Approach

When to Implement

Use an existing weak event manager class

If the event you want to subscribe to has a corresponding WeakEventManager, use the existing weak event manager. For a list of weak event managers that are included with WPF, see the inheritance hierarchy in the WeakEventManager class. Note, however, that there are relatively few weak event managers that are included with WPF, so you will probably need to choose one of the other approaches.

Use a generic weak event manager class

Use a generic WeakEventManager<TEventSource, TEventArgs> when an existing WeakEventManager is not available, you want an easy way to implement, and you are not concerned about efficiency. The generic WeakEventManager<TEventSource, TEventArgs> is less efficient than an existing or custom weak event manager. For example, the generic class does more reflection to discover the event given the event's name. Also, the code to register the event by using the generic WeakEventManager<TEventSource, TEventArgs> is more verbose than using an existing or custom WeakEventManager.

Create a custom weak event manager class

Create a custom WeakEventManager when you an existing WeakEventManager is not available and you want the best efficiency. Using a custom WeakEventManager to subscribe to an event will be more efficient, but you do incur the cost of writing more code at the beginning.

The following sections describe how to implement the weak event pattern. For purposes of this discussion, the event to subscribe to has the following characteristics.

  • The event name is SomeEvent.

  • The event is raised by the EventSource class.

  • The event handler has type: SomeEventEventHandler (or EventHandler<SomeEventEventArgs>).

  • The event passes a parameter of type SomeEventEventArgs to the event handlers.

Using an Existing Weak Event Manager Class

  1. Find an existing weak event manager.

    For a list of weak event managers that are included with WPF, see the inheritance hierarchy in the WeakEventManager class.

  2. Use the new weak event manager instead of the normal event hookup.

    For example, if your code uses the following pattern to subscribe to an event:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Change it to the following pattern:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Similarly, if your code uses the following pattern to unsubscribe to an event:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Change it to the following pattern:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);
    

Using the Generic Weak Event Manager Class

  1. Use the generic WeakEventManager<TEventSource, TEventArgs> class instead of the normal event hookup.

    When you use WeakEventManager<TEventSource, TEventArgs> to register event listeners, you supply the event source and EventArgs type as the type parameters to the class and call AddHandler as shown in the following code:

    WeakEventManager<EventSource, SomeEventEventArgs>.AddHandler(source, "SomeEvent", source_SomeEvent);
    

Creating a Custom Weak Event Manager Class

  1. Copy the following class template to your project.

    This class inherits from the WeakEventManager class.

    class SomeEventWeakEventManager : WeakEventManager
    {
    
        private SomeEventWeakEventManager()
        {
    
        }
    
        /// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(EventSource source, 
                                      EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedAddHandler(source, handler);
        }
    
        /// <summary>
        /// Remove a handler for the given source's event.
        /// </summary>
        public static void RemoveHandler(EventSource source, 
                                         EventHandler<SomeEventEventArgs> handler)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
    
            CurrentManager.ProtectedRemoveHandler(source, handler);
        }
    
        /// <summary>
        /// Get the event manager for the current thread.
        /// </summary>
        private static SomeEventWeakEventManager CurrentManager
        {
            get
            {
                Type managerType = typeof(SomeEventWeakEventManager);
                SomeEventWeakEventManager manager = 
                    (SomeEventWeakEventManager)GetCurrentManager(managerType);
    
                // at first use, create and register a new manager
                if (manager == null)
                {
                    manager = new SomeEventWeakEventManager();
                    SetCurrentManager(managerType, manager);
                }
    
                return manager;
            }
        }
    
    
    
        /// <summary>
        /// Return a new list to hold listeners to the event.
        /// </summary>
        protected override ListenerList NewListenerList()
        {
            return new ListenerList<SomeEventEventArgs>();
        }
    
    
        /// <summary>
        /// Listen to the given source for the event.
        /// </summary>
        protected override void StartListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent += new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Stop listening to the given source for the event.
        /// </summary>
        protected override void StopListening(object source)
        {
            EventSource typedSource = (EventSource)source;
            typedSource.SomeEvent -= new EventHandler<SomeEventEventArgs>(OnSomeEvent);
        }
    
        /// <summary>
        /// Event handler for the SomeEvent event.
        /// </summary>
        void OnSomeEvent(object sender, SomeEventEventArgs e)
        {
            DeliverEvent(sender, e);
        }
    }
    
  2. Replace the SomeEventWeakEventManager name with your own name.

  3. Replace the three names described previously with the corresponding names for your event. (SomeEventEventSource, and SomeEventEventArgs)

  4. Set the visibility (public / internal / private) of the weak event manager class to the same visibility as event it manages.

  5. Use the new weak event manager instead of the normal event hookup.

    For example, if your code uses the following pattern to subscribe to an event:

    source.SomeEvent += new SomeEventEventHandler(OnSomeEvent);
    

    Change it to the following pattern:

    SomeEventWeakEventManager.AddHandler(source, OnSomeEvent);
    

    Similarly, if your code uses the following pattern to unsubscribe to an event:

    source.SomeEvent -= new SomeEventEventHandler(OnSome);
    

    Change it to the following pattern:

    SomeEventWeakEventManager.RemoveHandler(source, OnSomeEvent);




How-to Topics


How to: Add an Event Handler Using Code


This example shows how to add an event handler to an element by using code.

If you want to add an event handler to a XAML element, and the markup page that contains the element has already been loaded, you must add the handler using code. Alternatively, if you are building up the element tree for an application entirely using code and not declaring any elements using XAML, you can call specific methods to add event handlers to the constructed element tree.

Example

The following example adds a new Button to an existing page that is initially defined in XAML. A code-behind file implements an event handler method and then adds that method as a new event handler on the Button.

The C# example uses the += operator to assign a handler to an event. This is the same operator that is used to assign a handler in the common language runtime (CLR) event handling model. Microsoft Visual Basic does not support this operator as a means of adding event handlers. It instead requires one of two techniques:

  • Use the AddHandler method, together with an AddressOf operator, to reference the event handler implementation.

  • Use the Handles keyword as part of the event handler definition. This technique is not shown here; see Visual Basic and WPF Event Handling.

<TextBlock Name="text1">Start by clicking the button below</TextBlock>
<Button Name="b1" Click="MakeButton">Make new button and add handler to it</Button>
public partial class RoutedEventAddRemoveHandler {
    void MakeButton(object sender, RoutedEventArgs e)
    {
        Button b2 = new Button();
        b2.Content = "New Button";
        // Associate event handler to the button. You can remove the event 
        // handler using "-=" syntax rather than "+=".
        b2.Click  += new RoutedEventHandler(Onb2Click);
        root.Children.Insert(root.Children.Count, b2);
        DockPanel.SetDock(b2, Dock.Top);
        text1.Text = "Now click the second button...";
        b1.IsEnabled = false;
    }
    void Onb2Click(object sender, RoutedEventArgs e)
    {
        text1.Text = "New Button (b2) Was Clicked!!";
    }
System_CAPS_noteNote

Adding an event handler in the initially parsed XAML page is much simpler. Within the object element where you want to add the event handler, add an attribute that matches the name of the event that you want to handle. Then specify the value of that attribute as the name of the event handler method that you defined in the code-behind file of the XAML page. For more information, see XAML Overview (WPF) or Routed Events Overview.




How to: Handle a Routed Event


This example shows how bubbling events work and how to write a handler that can process the routed event data.

Example

In Windows Presentation Foundation (WPF), elements are arranged in an element tree structure. The parent element can participate in the handling of events that are initially raised by child elements in the element tree. This is possible because of event routing.

Routed events typically follow one of two routing strategies, bubbling or tunneling. This example focuses on the bubbling event and uses the ButtonBase.Click event to show how routing works.

The following example creates two Button controls and uses XAML attribute syntax to attach an event handler to a common parent element, which in this example is StackPanel. Instead of attaching individual event handlers for each Button child element, the example uses attribute syntax to attach the event handler to the StackPanel parent element. This event-handling pattern shows how to use event routing as a technique for reducing the number of elements where a handler is attached. All the bubbling events for each Button route through the parent element.

Note that on the parent StackPanel element, the Click event name specified as the attribute is partially qualified by naming the Button class. The Buttonclass is a ButtonBase derived class that has the Click event in its members listing. This partial qualification technique for attaching an event handler is necessary if the event that is being handled does not exist in the members listing of the element where the routed event handler is attached.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.RoutedEventHandle"
  Name="dpanel"
  Button.Click="HandleClick"
>
  <StackPanel.Resources>
      <Style TargetType="{x:Type Button}">
        <Setter Property="Height" Value="20"/>
        <Setter Property="Width" Value="250"/>
        <Setter Property="HorizontalAlignment" Value="Left"/>
      </Style>
  </StackPanel.Resources>
  <Button Name="Button1">Item 1</Button>
  <Button Name="Button2">Item 2</Button>
  <TextBlock Name="results"/>
</StackPanel>

The following example handles the Click event. The example reports which element handles the event and which element raises the event. The event handler is executed when the user clicks either button.

public partial class RoutedEventHandle : StackPanel
{
    StringBuilder eventstr = new StringBuilder();
    void HandleClick(object sender, RoutedEventArgs args)
    {
        // Get the element that handled the event.
        FrameworkElement fe = (FrameworkElement)sender;
        eventstr.Append("Event handled by element named ");
        eventstr.Append(fe.Name);
        eventstr.Append("\n");

        // Get the element that raised the event. 
        FrameworkElement fe2 = (FrameworkElement)args.Source;
        eventstr.Append("Event originated from source element of type ");
        eventstr.Append(args.Source.GetType().ToString());
        eventstr.Append(" with Name ");
        eventstr.Append(fe2.Name);
        eventstr.Append("\n");

        // Get the routing strategy.
        eventstr.Append("Event used routing strategy ");
        eventstr.Append(args.RoutedEvent.RoutingStrategy);
        eventstr.Append("\n");

        results.Text = eventstr.ToString();
    }
}




How to: Create a Custom Routed Event


For your custom event to support event routing, you need to register a RoutedEvent using the RegisterRoutedEvent method. This example demonstrates the basics of creating a custom routed event.

Example

As shown in the following example, you first register a RoutedEvent using the RegisterRoutedEvent method. By convention, the RoutedEvent static field name should end with the suffix Event. In this example, the name of the event is Tap and the routing strategy of the event is Bubble. After the registration call, you can provide add-and-remove common language runtime (CLR) event accessors for the event.

Note that even though the event is raised through the OnTap virtual method in this particular example, how you raise your event or how your event responds to changes depends on your needs.

Note also that this example basically implements an entire subclass of Button; that subclass is built as a separate assembly and then instantiated as a custom class on a separate Extensible Application Markup Language (XAML) page. This is to illustrate the concept that subclassed controls can be inserted into trees composed of other controls, and that in this situation, custom events on these controls have the very same event routing capabilities as any native Windows Presentation Foundation (WPF) element does.

public class MyButtonSimple: Button
{
    // Create a custom routed event by first registering a RoutedEventID
    // This event uses the bubbling routing strategy
    public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
        "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

    // Provide CLR accessors for the event
    public event RoutedEventHandler Tap
    {
            add { AddHandler(TapEvent, value); } 
            remove { RemoveHandler(TapEvent, value); }
    }

    // This method raises the Tap event
    void RaiseTapEvent()
    {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(MyButtonSimple.TapEvent);
            RaiseEvent(newEventArgs);
    }
    // For demonstration purposes we raise the event when the MyButtonSimple is clicked
    protected override void OnClick()
    {
        RaiseTapEvent();
    }

}
<Window  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:SDKSample;assembly=SDKSampleLibrary"
    x:Class="SDKSample.RoutedEventCustomApp"

    >
    <Window.Resources>
      <Style TargetType="{x:Type custom:MyButtonSimple}">
        <Setter Property="Height" Value="20"/>
        <Setter Property="Width" Value="250"/>
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="Background" Value="#808080"/>
      </Style>
    </Window.Resources>
    <StackPanel Background="LightGray">
	    <custom:MyButtonSimple Name="mybtnsimple" Tap="TapHandler">Click to see Tap custom event work</custom:MyButtonSimple>
    </StackPanel>
</Window>

Tunneling events are created the same way, but with RoutingStrategy set to Tunnel in the registration call. By convention, tunneling events in WPF are prefixed with the word "Preview".

To see an example of how bubbling events work, see How to: Handle a Routed Event.




How to: Find the Source Element in an Event Handler


This example shows how to find the source element in an event handler.

Example

The following example shows a Click event handler that is declared in a code-behind file. When a user clicks the button that the handler is attached to, the handler changes a property value. The handler code uses the Source property of the routed event data that is reported in the event arguments to change the Width property value on the Source element.

<Button Click="HandleClick">Button 1</Button>
     void HandleClick(object sender, RoutedEventArgs e)
     {
         // You must cast the sender object as a Button element, or at least as FrameworkElement, to set Width
         Button srcButton = e.Source as Button;
srcButton.Width = 200;
     }




How to: Add Class Handling for a Routed Event


Routed events can be handled either by class handlers or instance handlers on any given node in the route. Class handlers are invoked first, and can be used by class implementations to suppress events from instance handling or introduce other event specific behaviors on events that are owned by base classes. This example illustrates two closely related techniques for implementing class handlers.

Example

This example uses a custom class based on the Canvas panel. The basic premise of the application is that the custom class introduces behaviors on its child elements, including intercepting any left mouse button clicks and marking them handled, before the child element class or any instance handlers on it will be invoked.

The UIElement class exposes a virtual method that enables class handling on the PreviewMouseLeftButtonDown event, by simply overriding the event. This is the simplest way to implement class handling if such a virtual method is available somewhere in your class' hierarchy. The following code shows the OnPreviewMouseLeftButtonDown implementation in the "MyEditContainer" that is derived from Canvas. The implementation marks the event as handled in the arguments, and then adds some code to give the source element a basic visible change.

protected override void OnPreviewMouseRightButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
    e.Handled = true; //suppress the click event and other leftmousebuttondown responders
    MyEditContainer ec = (MyEditContainer)e.Source;
    if (ec.EditState)
    { ec.EditState = false; }
    else
    { ec.EditState = true; }
    base.OnPreviewMouseRightButtonDown(e);
}

If no virtual is available on base classes or for that particular method, class handling can be added directly using a utility method of the EventManagerclass, RegisterClassHandler. This method should only be called within the static initialization of classes that are adding class handling. This example adds another handler for PreviewMouseLeftButtonDown , and in this case the registered class is the custom class. In contrast, when using the virtuals, the registered class is really the UIElement base class. In cases where base classes and subclass each register class handling, the subclass handlers are invoked first. The behavior in an application would be that first this handler would show its message box and then the visual change in the virtual method's handler would be shown.

static MyEditContainer()
{
  EventManager.RegisterClassHandler(typeof(MyEditContainer), PreviewMouseRightButtonDownEvent, new RoutedEventHandler(LocalOnMouseRightButtonDown));
}
internal static void LocalOnMouseRightButtonDown(object sender, RoutedEventArgs e)
{
  MessageBox.Show("this is invoked before the On* class handler on UIElement");
  //e.Handled = true; //uncommenting this would cause ONLY the subclass' class handler to respond
}








'프로그래밍 > WPF' 카테고리의 다른 글

Documents  (0) 2016.11.05
Resources  (0) 2016.11.01
Element Tree and Serialization  (0) 2016.11.01
Base Elements (기본 요소)  (0) 2016.10.24
종속성 속성(Dependency Properties)  (0) 2016.10.24
:
Posted by 지훈2