Chapter 2. XAML Demystified

Throughout .NET technologies, XML is used to expose functionality in a transparent and declarative fashion. XAML, a dialect of XML, has been especially important since its introduction with the first version of WPF in 2006. It is often misunderstood to be just a way to specify user interfaces, much like HTML. By the end of this chapter, you will see that XAML is about much more than arranging controls on a computer screen.

In WPF and Silverlight, XAML is primarily used to describe user interfaces (although it is used to describe other things as well). In Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF), XAML is used to express activities and configurations that have nothing to do with user interfaces.

The point of XAML is to make it easy for programmers to work together with experts in other fields. XAML becomes the common language spoken by all parties, most likely via development tools and field-specific design tools. But because XAML (and XML in general) is generally human readable, people can participate in this ecosystem armed with nothing more than a tool such as Notepad.

In WPF and Silverlight, the “field experts” are graphic designers, who can use a design tool such as Expression Blend to create a slick user interface while developers independently write code. What enables the developer/designer cooperation is not just the common language of XAML but the fact that great care went into making functionality exposed by the relevant APIs accessible declaratively. This gives design tools a wide range of expressiveness (such as specifying complex animations or state changes) without having to worry about generating procedural code.

Even if you have no plans to work with graphic designers, you should still become familiar with XAML for the following reasons:

• XAML can be a very concise way to represent user interfaces or other hierarchies of objects.

• The use of XAML encourages a separation of front-end appearance and back-end logic, which is helpful for maintenance even if you’re only a team of one.

• XAML can often be easily pasted into tools such as Visual Studio, Expression Blend, or small standalone tools to see results without any compilation.

• XAML is the language that almost all WPF-related tools emit.

This chapter jumps right into the mechanics of XAML, examining its syntax in depth and showing how it relates to procedural code. Unlike the preceding chapter, this is a fairly deep dive! Having this background knowledge before proceeding with the rest of the book will not only help you understand the code examples but give you better insight into why the APIs in each feature area were designed the way they were. This perspective can be helpful whether you are building WPF applications or controls, designing class libraries that you want to be XAML friendly, or building tools that consume and/or produce XAML (such as validation tools, localization tools, file format converters, designers, and so on).

Tip

There are several ways to run the XAML examples in this chapter, which you can download in electronic form with the rest of this book’s source code. For example, you can do the following:

• Save the content in a .xaml file and open it inside Internet Explorer (in Windows Vista or later, or in Windows XP with the .NET Framework 3.0 or later installed). Firefox can also work if you install an add-on. However, by default your web browser will use the version of WPF installed with the operating system rather than using WPF 4.

• Paste the content into a lightweight tool such as the XAMLPAD2009 sample included with this chapter’s source code or Kaxaml (from http://kaxaml.com), although the latter has not been updated to use WPF 4 at the time of writing.

• Create a WPF Visual Studio project and replace the content of the main Window or Page element with the desired content, which might require some code changes.

Using the first two options gives you a couple great ways to get started and do some experimentation. Mixing XAML with other content in a Visual Studio project is covered at the end of this chapter.

FAQ

image What happened to XamlPad?

Earlier versions of the Windows SDK shipped with a simple tool called XamlPad that allows you to type in (or paste) WPF-compatible XAML and see it rendered as a live user interface. Unfortunately, this tool is no longer being shipped due to lack of resources. (Yes, contrary to popular belief, Microsoft does not have unlimited resources!) Fortunately, there are several alternative lightweight tools for quickly experimenting with XAML, including the following:

XAMLPAD2009—A sample in this book’s source code. Although it lacks the bells and whistles of the other tools, it provides full source code. Plus, it’s the only tool that supports XAML2009 (explained later in this chapter) at the time of writing.

Kaxaml—A slick tool downloadable from http://kaxaml.com, created by Robby Ingebretsen, a former WPF team member.

XamlPadX—A feature-filled tool downloadable from http://blogs.msdn.com/llobo/archive/2008/08/25/xamlpadx-4-0.aspx, created by Lester Lobo, a current WPF team member.

XAML Cruncher—A ClickOnce application available at http://charlespetzold.com/wpf/XamlCruncher/XamlCruncher.application, created by Charles Petzold, prolific author and blogger.

XAML Defined

XAML is a relatively simple and general-purpose declarative programming language suitable for constructing and initializing objects. XAML is just XML, but with a set of rules about its elements and attributes and their mapping to objects, their properties, and the values of those properties (among other things).

Because XAML is just a mechanism for using .NET APIs, attempts to compare it to HTML, Scalable Vector Graphics (SVG), or other domain-specific formats/languages are misguided. XAML consists of rules for how parsers/compilers must treat XML and has some keywords, but it doesn’t define any interesting elements by itself. So, talking about XAML without a framework like WPF is like talking about C# without the .NET Framework. That said, Microsoft has formalized the notion of “XAML vocabularies” that define the set of valid elements for a given domain, such as what it means to be a WPF XAML file versus a Silverlight XAML file versus any other type of XAML file.

The role XAML plays in relation to WPF is often confused, so it’s important to reemphasize that WPF and XAML can be used independently from each other. Although XAML was originally designed for WPF, it is used by other technologies as well. Because of its general-purpose nature, XAML can be applied to just about any object-oriented technology if you really want it to be. Furthermore, using XAML in WPF projects is optional. Almost everything done with XAML can be done entirely in your favorite .NET procedural language instead. (But note that the reverse is not true.) However, because of the benefits listed at the beginning of the chapter, it’s rare to see WPF used in the real world without XAML.

Elements and Attributes

The XAML specification defines rules that map .NET namespaces, types, properties, and events into XML namespaces, elements, and attributes. You can see this by examining the following simple (but complete) XAML file that declares a WPF Button and comparing it to the equivalent C# code:

XAML:

image

C#:

image

Although these two snippets are equivalent, you can instantly view the XAML in Internet Explorer and see a live button fill the browser window, as pictured in Figure 2.1, whereas the C# code must be compiled with additional code to be usable.

Figure 2.1 A simple WPF Button declared in a .xaml file.

image

Declaring an XML element in XAML (known as an object element) is equivalent to instantiating the corresponding .NET object via a default constructor. Setting an attribute on the object element is equivalent to setting a property of the same name (called a property attribute) or hooking up an event handler of the same name (called an event attribute). For example, here’s an update to the Button that not only sets its Content property but also attaches an event handler to its Click event:

XAML:

image

C#:

image

This requires a method called button_Click to be defined somewhere, with the appropriate signature, which means that the XAML file can no longer be rendered standalone, as in Figure 2.1. The “Mixing XAML with Procedural Code” section at the end of this chapter explains how to work with XAML that requires additional code. Note that XAML, like C#, is a case-sensitive language.

Namespaces

The most mysterious part about comparing the previous XAML examples with the equivalent C# examples is how the XML namespace http://schemas.microsoft.com/winfx/2006/xaml/presentation maps to the .NET namespace System.Windows.Controls. It turns out that the mapping to this and other WPF namespaces is hard-coded inside the WPF assemblies with several instances of an XmlnsDefinitionAttribute custom attribute. (In case you’re wondering, no web page exists at the schemas.microsoft.com URL—it’s just an arbitrary string like any namespace.)

The root object element in a XAML file must specify at least one XML namespace that is used to qualify itself and any child elements. You can declare additional XML namespaces (on the root or on children), but each one must be given a distinct prefix to be used on any identifiers from that namespace. For example, WPF XAML files typically use a second namespace with the prefix x (denoted by using xmlns:x instead of just xmlns):

image

This is the XAML language namespace, which maps to types in the System.Windows.Markup namespace but also defines some special directives for the XAML compiler or parser. These directives often appear as attributes to XML elements, so they look like properties of the host element but actually are not. For a list of XAML keywords, see the “XAML Keywords” section later in this chapter.

Tip

Most of the standalone XAML examples in this chapter explicitly specify their namespaces, but in the remainder of the book, most examples assume that the WPF XML namespace (http://schemas.microsoft.com/winfx/2006/xaml/presentation) is declared as the primary namespace, and the XAML language namespace (http://schemas.microsoft.com/winfx/2006/xaml) is declared as a secondary namespace, with the prefix x. If you want to view such content in your web browser or copy it into a lightweight viewer such as the XAMLPAD2009 sample, be sure to add these explicitly.

Using the WPF XML namespace (http://schemas.microsoft.com/winfx/2006/xaml/presentation) as a default namespace and the XAML language namespace (http://schemas.microsoft.com/winfx/2006/xaml) as a secondary namespace with the prefix x is just a convention, just like it’s a convention to begin a C# file with a using System; directive. You could instead write the original XAML file as follows, and it would mean the same thing:

image

Of course, for readability it makes sense for your most commonly used namespace (also known as the primary XML namespace) to be prefix free and to use short prefixes for any additional namespaces.

Property Elements

The preceding chapter mentioned that rich composition is one of the highlights of WPF. This can be demonstrated with the simple Button from Figure 2.1, because you can put arbitrary content inside it; you’re not limited to just text! To demonstrate this, the following code embeds a simple square to make a Stop button like what might be found in a media player:

image

Button’s Content property is of type System.Object, so it can easily be set to the 40×40 Rectangle object. The result is pictured in Figure 2.2.

Figure 2.2 Updating the WPF Button with complex content.

image

That’s pretty neat, but how can you do the same thing in XAML with property attribute syntax? What kind of string could you possibly set Content to that is equivalent to the preceding Rectangle declared in C#? There is no such string, but XAML fortunately provides an alternative (and more verbose) syntax for setting complex property values: property elements. It looks like the following:

image

The Content property is now set with an XML element instead of an XML attribute, making it equivalent to the previous C# code. The period in Button.Content is what distinguishes property elements from object elements. Property elements always take the form TypeName.PropertyName, they are always contained inside a TypeName object element, and they can never have attributes of their own (with one exception—the x:Uid attribute used for localization).

Property element syntax can be used for simple property values as well. The following Button that sets two properties with attributes (Content and Background):

image

is equivalent to this Button, which sets the same two properties with elements:

image

Of course, using attributes when you can is a nice shortcut when hand-typing XAML.

Type Converters

Let’s look at the C# code equivalent to the preceding Button declaration that sets both Content and Background properties:

image

Wait a minute. How can "White" in the previous XAML file be equivalent to the static System.Windows.Media.Brushes.White field (of type System.Windows.Media.SolidColorBrush) in the C# code? Indeed, this example exposes a subtlety with using strings to set properties in XAML that are a different data type than System.String or System.Object. In such cases, the XAML parser or compiler must look for a type converter that knows how to convert the string representation to the desired data type.

WPF provides type converters for many common data types: Brush, Color, FontWeight, Point, and so on. They are all classes deriving from TypeConverter (BrushConverter, ColorConverter, and so on). You can also write your own type converters for custom data types. Unlike the XAML language, type converters generally support case-insensitive strings.

Without a type converter for Brush, you would have to use property element syntax to set the Background in XAML, as follows:

image

And even that is only possible because of a type converter for Color that can make sense of the "White" string. If there were no Color type converter, you could still write the following:

image

But this is only possible because of a type converter that can convert each "255" string into a Byte value expected by the A, R, G, and B properties of the Color type. Without this type converter, you would basically be stuck. Type converters don’t just enhance the readability of XAML, they also enable values to be expressed that couldn’t otherwise be expressed.

Markup Extensions

Markup extensions, like type converters, enable you to extend the expressiveness of XAML. Both can evaluate a string attribute value at runtime (except for a few built-in markup extensions that are currently evaluated at compile time for performance reasons) and produce an appropriate object based on the string. As with type converters, WPF ships with several markup extensions built in.

Unlike type converters, however, markup extensions are invoked from XAML with explicit and consistent syntax. For this reason, using markup extensions is a preferred approach for extending XAML. In addition, using markup extensions enables you to overcome potential limitations in existing type converters that you don’t have the power to change. For example, if you want to set a control’s background to a fancy gradient brush with a simple string value, you can write a custom markup extension that supports it even though the built-in BrushConverter does not.

Whenever an attribute value is enclosed in curly braces ({}), the XAML compiler/parser treats it as a markup extension value rather than a literal string (or something that needs to be type-converted). The following Button uses three different markup extension values with three different properties:

image

The first identifier in each set of curly braces is the name of the markup extension class, which must derive from a class called MarkupExtension. By convention, such classes end with an Extension suffix, but you can leave it off when using it in XAML. In this example, NullExtension (seen as x:Null) and StaticExtension (seen as x:Static) are classes in the System.Windows.Markup namespace, so the x prefix must be used to locate them. Binding (which doesn’t happen to have the Extension suffix) is in the System.Windows.Data namespace, so it can be found in the default XML namespace.

If a markup extension supports them, comma-delimited parameters can be specified. Positional parameters (such as SystemParameters.IconHeight in the example) are treated as string arguments for the extension class’s appropriate constructor. Named parameters (Path and RelativeSource in the example) enable you to set properties with matching names on the constructed extension object. The values for these properties can be markup extension values themselves (using nested curly braces, as done with the value for RelativeSource) or literal values that can undergo the normal type conversion process. If you’re familiar with .NET custom attributes (the .NET Framework’s popular extensibility mechanism), you’ve probably noticed that the design and usage of markup extensions closely mirrors the design and usage of custom attributes. That is intentional.

In the preceding Button declaration, NullExtension enables the Background brush to be set to null, which isn’t natively supported by BrushConverter (or many other type converters, for that matter). This is just done for demonstration purposes, as a null Background is not very useful. StaticExtension enables the use of static properties, fields, constants, and enumeration values rather than hard-coding literals in XAML. In this case, the Button’s Height is set to the operating system’s current height setting for icons, exposed by the static IconHeight property on a System.Windows.SystemParameters class. Binding, covered in depth in Chapter 13, “Data Binding,” enables Content to be set to the same value as the Height property.

Because markup extensions are just classes with default constructors, they can be used with property element syntax. The following Button is identical to the preceding one:

image

This transformation works because these markup extensions all have properties corresponding to their parameterized constructor arguments (the positional parameters used with property attribute syntax). For example, StaticExtension has a Member property that has the same meaning as the argument that was previously passed to its parameterized constructor, and RelativeSource has a Mode property that corresponds to its constructor argument.

Children of Object Elements

A XAML file, like all XML files, must have a single root object element. Therefore, it should come as no surprise that object elements can support child object elements (not just property elements, which aren’t children, as far as XAML is concerned). An object element can have three types of children: a value for a content property, collection items, or a value that can be type-converted to the object element.

The Content Property

Most WPF classes designate a property (via a custom attribute) that should be set to whatever content is inside the XML element. This property is called the content property, and it is really just a convenient shortcut to make the XAML representation more compact. In some ways, these content properties are like the (often-maligned) default properties in old versions of Visual Basic.

Button’s Content property is (appropriately) given this special designation, so the following Button:

image

could be rewritten as follows:

image

Or, more usefully, this Button with more complex content:

image

could be rewritten as follows:

image

There is no requirement that the content property must actually be called Content; classes such as ComboBox, ListBox, and TabControl (also in the System.Windows.Controls namespace) use their Items property as the content property.

Collection Items

XAML enables you to add items to the two main types of collections that support indexing: lists and dictionaries.

Lists

A list is any collection that implements System.Collections.IList, such as System.Collections.ArrayList or numerous collection classes defined by WPF. For example, the following XAML adds two items to a ListBox control whose Items property is an ItemCollection that implements IList:

image

This is equivalent to the following C# code:

image

Furthermore, because Items is the content property for ListBox, you can shorten the XAML even further, as follows:

image

In all these cases, the code works because ListBox’s Items property is automatically initialized to any empty collection object. If a collection property is initially null instead (and is read/write, unlike ListBox’s read-only Items property), you need to wrap the items in an explicit element that instantiates the collection. WPF’s built-in controls do not act this way, so an imaginary OtherListBox element demonstrates what this could look like:

image

Dictionaries

System.Windows.ResourceDictionary is a commonly used collection type in WPF that you’ll see more of in Chapter 12, “Resources.” It implements System.Collections.IDictionary, so it supports adding, removing, and enumerating key/value pairs in procedural code, as you would do with a typical hash table. In XAML, you can add key/value pairs to any collection that implements IDictionary. For example, the following XAML adds two Colors to a ResourceDictionary:

image

This leverages the XAML Key keyword (defined in the secondary XML namespace), which is processed specially and enables us to attach a key to each Color value. (The Color type does not define a Key property.) Therefore, the XAML is equivalent to the following C# code:

image

Note that the value specified in XAML with x:Key is treated as a string unless a markup extension is used or the XAML2009 parser is used (see the later “Introducing XAML2009” section); no type conversion is attempted otherwise.

More Type Conversion

Plain text can often be used as the child of an object element, as in the following XAML declaration of SolidColorBrush:

image

This is equivalent to the following:

image

even though Color has not been designated as a content property. In this case, the first XAML snippet works because a type converter exists that can convert strings such as "White" (or "white" or "#FFFFFF") into a SolidColorBrush object.

Although type converters play a huge role in making XAML readable, the downside is that they can make XAML appear a bit “magical,” and it can be difficult to understand how it maps to instances of .NET objects. Using what you know so far, it would be reasonable to assume that you can’t declare an abstract class element in XAML because there’s no way to instantiate it. However, even though System.Windows.Media.Brush is an abstract base class for SolidColorBrush, GradientBrush, and other concrete brushes, you can express the preceding XAML snippets as simply:

image

because the type converter for Brushes understands that this is still SolidColorBrush. This may seem like an unusual feature, but it’s important for supporting the ability to express primitive types in XAML, as demonstrated in “The Extensible Part of XAML.”

Mixing XAML with Procedural Code

WPF applications can be written entirely in procedural code in any .NET language. In addition, certain types of simple applications can be written entirely in XAML, thanks to the data-binding features described in Chapter 13, the triggers introduced in the next chapter, and the fact that loose XAML pages can be rendered in a web browser. However, most WPF applications are a mix of XAML and procedural code. This section covers the two ways that XAML and code can be mixed together.

Loading and Parsing XAML at Runtime

WPF has a runtime XAML parser exposed as two classes in the System.Windows.Markup namespace: XamlReader and XamlWriter. And their APIs couldn’t be much simpler. XamlReader contains a few overloads of a static Load method, and XamlWriter contains a few overloads of a static Save method. Therefore, programs written in any .NET language can leverage XAML at runtime without much effort. The .NET Framework 4.0 ships a new, separate set of XAML readers and writers but with a fair number of caveats. They are not important for this discussion but are covered later, in the “Fun with XAML Readers and Writers” section.

XamlReader

The set of XamlReader.Load methods parse XAML, create the appropriate .NET objects, and return an instance of the root element. So, if a XAML file named MyWindow.xaml in the current directory contains a Window object (explained in depth in Chapter 7, Structuring and Deploying an Application”) as its root node, the following code could be used to load and retrieve the Window object:

image

In this case, Load is called with a FileStream (from the System.IO namespace). After Load returns, the entire hierarchy of objects in the XAML file is instantiated in memory, so the XAML file is no longer needed. In the preceding code, the FileStream is instantly closed by exiting the using block. Because XamlReader can be passed an arbitrary Stream (or System.Xml.XmlReader, via a different overload), you have a lot of flexibility in retrieving XAML content.

Tip

XamlReader also defines LoadAsync instance methods that load and parse XAML content asynchronously. You’ll want to use LoadAsync to keep a responsive user interface during the loading of large files or files over the network, for example. Accompanying these methods are a CancelAsync method for halting the processing and a LoadCompleted event for knowing when the processing is complete.

The behavior of LoadAsync is a bit odd, however. The work is done on the UI thread via multiple Dispatcher.BeginInvoke calls. (WPF tries to break the work up into 200-millisecond chunks.) Furthermore, this asynchronous processing is only used if x:SynchronousMode="Async" is set on the root XAML node. If this attribute is not set, LoadAsync will silently load the XAML synchronously.

Now that an instance of the root element exists, you can retrieve child elements by making use of the appropriate content properties or collection properties. The following code assumes that the Window has a StackPanel object as its content, whose fifth child is an OK Button:

image

With a reference to the Button, you can do whatever you want: Set additional properties (perhaps using logic that is hard or impossible to express in XAML), attach event handlers, or perform additional actions that you can’t do from XAML, such as calling its methods.

Of course, the code that uses a hard-coded index and other assumptions about the user interface structure isn’t very satisfying, as simple changes to the XAML can break it. Instead, you could write code to process the elements more generically and look for a Button element whose content is an "OK" string, but that would be a lot of work for such a simple task. In addition, if you want the Button to contain graphical content, how can you easily identify it in the presence of multiple Buttons?

Fortunately, XAML supports naming of elements so they can be found and used reliably from procedural code.

Naming XAML Elements

The XAML language namespace has a Name keyword that enables you to give any element a name. For the simple OK button that we’re imagining is embedded somewhere inside a Window, the Name keyword can be used as follows:

image

With this in place, you can update the preceding C# code to use Window’s FindName method that searches its children (recursively) and returns the desired instance:

image

FindName is not unique to Window; it is defined on FrameworkElement and FrameworkContentElement, which are base classes for many important classes in WPF.

Tip

In all versions of WPF, the Binding markup extension can be used to reference a named element as a property value:

image

(In this case, assigning the TextBox as the Target of the Label gives it focus when the Label’s access key, Alt+T, is pressed.) WPF 4 includes a new, simpler markup extension (that finds the element at parse time rather than runtime): System.Windows.Markup.Reference. It can be used as follows:

image

Or, when a relevant property is marked with the System.Windows.Markup.NameReferenceConverter type converter (as in this case), a simple name string can be implicitly converted into the referenced instance:

image

Compiling XAML

Loading and parsing XAML at runtime is interesting for dynamic skinning scenarios or for .NET languages that don’t have the necessary support for XAML compilation. Most WPF projects, however, leverage the XAML compilation supported by MSBuild and Visual Studio. XAML compilation involves three things: converting a XAML file into a special binary format, embedding the converted content as a binary resource in the assembly being built, and performing the plumbing that connects XAML with procedural code automatically. C# and Visual Basic are the two languages with the best support for XAML compilation.

If you don’t care about mixing procedural code with your XAML file, then all you need to do to compile it is add it to a WPF project in Visual Studio with a Build Action of Page. (Chapter 7 explains ways to make use of such content in the context of an application.) But for the typical case of compiling a XAML file and mixing it with procedural code, the first step is specifying a subclass for the root element in a XAML file. This can be done with the Class keyword defined in the XAML language namespace, for example:

image

In a separate source file (but in the same project), you can define the subclass and add whatever members you want:

image

This is often referred to as the code-behind file. If you reference any event handlers in XAML (via event attributes such as Click on Button), this is where they should be defined.

The partial keyword in the class definition is important, as the class’s implementation is spread across more than one file. If the .NET language doesn’t support partial classes (for example, C++/CLI and J#), the XAML file must also use a Subclass keyword in the root element, as follows:

image

With this change, the XAML file completely defines the Subclass (MyWindow2 in this case) but uses the Class in the code-behind file (MyWindow) as its base class. Therefore, this simulates the ability to split the implementation across two files by relying on inheritance.

When creating a WPF-based C# or Visual Basic project in Visual Studio, or when you use Add New Item... to add certain WPF items to a project, Visual Studio automatically creates a XAML file with x:Class on its root, creates the code-behind source file with the partial class definition, and links the two together so they are built properly.

If you’re an MSBuild user and want to understand the contents of the project file that enables code-behind, you can open any of the C# project files included with this book’s source code in a simple text editor such as Notepad. The relevant part of a typical project is as follows:

image

Tip

x:Class can only be used in a XAML file that gets compiled. But you can sometimes compile a XAML file with no x:Class just fine. This simply means that there is no corresponding code-behind file, so you can’t use any features that rely on the presence of procedural code. Therefore, adding a XAML file to a Visual Studio project without an x:Class directive can be a handy way to get the deployment and performance benefits of compiled XAML without having to create an unnecessary code-behind file.

For such a project, the build system generates several items when processing MyWindow.xaml, including these:

• A BAML file (MyWindow.baml), which gets embedded in the assembly as a binary resource by default.

• A C# source file (MyWindow.g.cs), which gets compiled into the assembly like all other source code.

BAML

BAML, which stands for Binary Application Markup Language, is simply XAML that has been parsed, tokenized, and converted into binary form. Although almost any chunk of XAML can be represented by procedural code, the XAML-to-BAML compilation process does not generate procedural source code. So, BAML is not like Microsoft intermediate language (MSIL); it is a compressed declarative format that is faster to load and parse (and smaller in size) than plain XAML. BAML is basically an implementation detail of the XAML compilation process. Nevertheless, it’s interesting to be aware of its existence. In fact, WPF 4 contains a public BAML reader class (see the “Fun with XAML Readers and Writers” section).

Generated Source Code

Some procedural code does get generated in the XAML compilation process (if you use x:Class), but it’s just some “glue code” similar to what had to be written to load and parse a loose XAML file at runtime. Such files are given a suffix such as .g.cs (or .g.vb), where the g stands for generated.

Each generated source file contains a partial class definition for the class specified with x:Class on the root object element. This partial class contains a field (internal by default) for every named element in the XAML file, using the element name as the field name. It also contains an InitializeComponent method that does the grunt work of loading the embedded BAML resource, assigning the fields to the appropriate instances originally declared in XAML, and hooking up any event handlers (if any event handlers were specified in the XAML file).

Because the glue code tucked away in the generated source file is part of the same class you’ve defined in the code-behind file (and because BAML gets embedded as a resource), you often don’t need to be aware of the existence of BAML or the process of loading and parsing it. You simply write code that references named elements just like any other class member, and you let the build system worry about hooking things together. The only thing you need to remember is to call InitializeComponent in your code-behind class’s constructor.

Warning: Don’t forget to call InitializeComponent in the constructor of your code-behind class!

If you fail to do so, your root element won’t contain any of the content you defined in XAML (because the corresponding BAML doesn’t get loaded), and all the fields representing named object elements will be null.

FAQ

image Can BAML be decompiled back into XAML?

Sure, because BAML can be converted into a graph of live object instances, and these instances can be serialized as XAML, regardless of how they were originally declared.

The first step is to retrieve an instance that you want to be the root of the XAML. If you don’t already have this object, you can call the static System.Windows.Application.LoadComponent method to load it from BAML, as follows:

image

Yes, that code is loading BAML despite the .xaml suffix. This differs from previous code that uses FileStream to load a .xaml file because with LoadComponent, the name specified as the uniform resource identifier (URI) does not have to physically exist as a standalone .xaml file. LoadComponent can automatically retrieve BAML embedded as a resource when given the appropriate URI (which, by MSBuild convention, is the name of the original XAML source file). In fact, Visual Studio’s autogenerated InitializeComponent method calls Application.LoadComponent to load embedded BAML, although it uses a different overload. Chapter 12 provides more details about this mechanism of retrieving embedded resources with URIs.

After you’ve gotten a hold of the root element instance, you can use the System.Windows.Markup.XamlWriter class to get a XAML representation of the root element (and, therefore, all its children). XamlWriter contains five overloads of a static Save method, the simplest of which accepts an object instance and returns appropriate XAML as a string:

image

It might sound a little troubling that BAML can be so easily “cracked open,” but it’s really no different from any other software running locally or displaying a user interface locally. (For example, you can easily dig into a website’s HTML, JavaScript, and Cascading Style Sheets [CSS] files.) The popular .NET Reflector tool has a BamlViewer add-in (see http://codeplex.com/reflectoraddins) that displays BAML embedded in any assembly as XAML.

Introducing XAML2009

Although XAML is a general-purpose language whose use is broader than that of WPF, WPF’s XAML compiler and parsers are architecturally tied to WPF. Therefore, they are not usable by other technologies without taking a dependency on WPF. The .NET Framework 4.0 fixes this by introducing a new System.Xaml assembly that contains a bunch of functionality for processing XAML. WPF (and WCF and WF) take a dependency on System.Xaml—not the other way around.

At the same time, the .NET Framework 4.0 introduces a handful of new features for the XAML language. This second generation of the XAML language is referred to as XAML2009. (To differentiate, the first generation is sometimes referred to as XAML2006.) The System.Xaml assembly supports XAML2009, unlike the older APIs (System.Windows.Markup.XamlReader and System.Windows.Markup.XamlWriter from the previous section), which only support XAML2006.

The new XAML2009 features, outlined in this section, are nothing revolutionary but represent a nice set of incremental improvements to XAML. However, don’t get too excited; for the most part, these features are not usable in WPF projects because XAML compilation still uses the XAML2006-based APIs, as do Visual Studio’s WPF designer and editor, due to schedule constraints.

At the time of writing, it is unclear when WPF will completely switch over to XAML2009. (Note that Silverlight doesn’t support XAML2009 either; it doesn’t even support the entire XAML2006 specification!) In WPF 4, however, you can take advantage of these features when using loose XAML with a host that processes the XAML with the XAML2009-based APIs, such as the XAMLPAD2009 sample from this book’s source code or Internet Explorer when the netfx/2009 XML namespace is used.

Therefore, the XAML2009 features are interesting to learn about, even if they are not yet terribly useful. Most of them revolve around the idea of making a wider range of types directly usable from XAML. This is good news for class library authors, as XAML2009 imposes fewer restrictions for making class libraries XAML friendly. On its own, each feature provides a small improvement in expressiveness, but many of the features work together to solve real-world problems.

Full Generics Support

In XAML2006, the root element can be an instantiation of a generic class, thanks to the x:TypeArguments keyword. x:TypeArguments can be set to a type name or a comma-delimited list of type names. But because x:TypeArguments can only be used on the root element, generic classes generally have not been XAML friendly.

A common workaround for this limitation is to derive a non-generic class from a generic one simply so it can be referenced from XAML, as in the following example:

C#:

image

XAML:

image

In XAML2009, however, x:TypeArguments can be used on any element, so a class like ObservableCollection<Photo> can be instantiated directly from XAML:

image

In this case, collections is assumed to map to the System.Collections.ObjectModel namespace that contains ObservableCollection.

Dictionary Keys of Any Type

In XAML2009, type conversion is now attempted with x:Key values, so you can successfully add items to a dictionary with non-string keys without using a markup extension. Here’s an example:

image

Here, collections is assumed to map to the System.Collections.Generic namespace.

Built-In System Data Types

In XAML2006, using core .NET data types such as String or Int32 is awkward due to the need to reference the System namespace from the mscorlib assembly, as seen previously in this chapter:

image

In XAML2009, 13 .NET data types have been added to the XAML language namespace that most XAML is already referencing. With a namespace prefix of x, these data types are x:Byte, x:Boolean, x:Int16, x:Int32, x:Int64, x:Single, x:Double, x:Decimal, x:Char, x:String, x:Object, x:Uri, and x:TimeSpan. Therefore, the previous snippet can be rewritten as follows:

image

But it is typically seen as follows in a XAML file already referencing the XAML language namespace:

image

Instantiating Objects with Non-Default Constructors

XAML2009 introduces an x:Arguments keyword that enables you to specify one or more arguments to pass to a class’s constructor. Consider, for example, the System.Version class, which has a default constructor and four parameterized constructors. You could not construct an instance of this class in XAML2006 unless someone provided an appropriate type converter (or unless you were happy with the behavior of the default constructor, which produces a version number of 0.0).

In XAML2009, you can instantiate this class with its constructor that accepts a single string as follows:

image

The constructor argument doesn’t have to be a string; the attribute value undergoes type conversion as necessary.

Unlike x:TypeArguments, x:Arguments does not allow you to specify multiple arguments in the attribute value with a comma-delimited string. Instead, you can use the element form of x:Arguments to specify any number of arguments. For example, calling System.Version’s constructor that accepts four integers can be done as follows:

image

Getting Instances via Factory Methods

With the new x:FactoryMethod keyword in XAML2009, you can now get an instance of a class that doesn’t have any public constructors. x:FactoryMethod enables you to specify any public static method that returns an instance of the desired type. For example, the following XAML uses a Guid instance returned by the static Guid.NewGuid method:

image

When x:FactoryMethod is used with x:Arguments, the arguments are passed to the static factory method rather than to a constructor. Therefore, the following XAML calls the static Marshal.GetExceptionForHR method, which accepts an HRESULT error code as input and returns the corresponding .NET exception that would be thrown by the common language runtime interoperability layer when encountering such an error:

image

Figure 2.3 shows the result of the previous two Labels stacked in the same XAML content, as rendered by the XAMLPAD2009 sample.

Figure 2.3 Displaying two instances retrieved via static factory methods.

image

Event Handler Flexibility

Event handlers can’t be assigned in a loose XAML2006 file, but they can be assigned in a loose XAML2009 file as long as the root instance can be located and it has a method with a matching name and appropriate signature. In addition, in XAML2009, the value of an event attribute can be any markup extension that returns an appropriate delegate:

image

As with any markup extension, it can accept arbitrary input and perform arbitrary logic to look up the delegate.

Defining New Properties

XAML is primarily focused on instantiating existing classes and setting values of their predefined properties. Two new elements in XAML2009—x:Members and the corresponding x:Property—enable the definition of additional properties directly inside XAML. This functionality doesn’t apply to WPF, however. You can see it used in Windows Workflow Foundation XAML, as in the following example:

image

Fun with XAML Readers and Writers

You have already seen how to read and write XAML with XamlReader.Load and XamlWriter.Save from the System.Windows.Markup namespace. These APIs have been around since the first version of WPF and still work just fine on WPF content—as long as that content stays within the XAML2006 subset.

The new System.Xaml assembly contains System.Xaml.XamlReader and System.Xaml.XamlWriter abstract base classes (not to be confused with the aforementioned reader/writer classes) that are the foundation of a new way to read and write XAML. The classes in System.Xaml are much more flexible than the “black box” conversion done by the older classes, and they support XAML2009.

Overview

XamlReader is designed to generate a stream of logical XAML nodes from an arbitrary source (dictated by the concrete derived implementation), and XamlWriter is designed to consume such a stream of XAML nodes and write them out in an arbitrary way. The following derived readers and writers are currently shipped as public classes:

Readers (derived from System.Xaml.XamlReader):

System.Xaml.XamlXmlReader—Reads XML (by working with a System.Xml.XmlReader, System.IO.TextReader, System.IO.Stream, or filename string)

System.Xaml.XamlObjectReader—Reads a live object graph

System.Windows.Baml2006.Baml2006Reader—Reads BAML (the 2006 form still used by WPF)

System.Xaml.XamlBackgroundReader—Wraps another XamlReader, implementing double-buffering so the reader can do its work on a separate thread from a writer

Writers (derived from System.Xaml.XamlWriter):

System.Xaml.XamlXmlWriter—Writes XML (using either a System.Xml.XmlWriter, System.IO.TextWriter, or Stream)

System.Xaml.XamlObjectWriter—Produces a live graph of objects

XAML readers and XAML writers work together much like the readers and writers elsewhere in the .NET Framework, such as ones in the System.IO and System.Xml namespaces. The result is an ecosystem in which many different readers and writers can be mixed and matched, where the notion of logical XAML nodes becomes the common connection. This is pictured in Figure 2.4, with the readers and writers that ship with the .NET Framework. The XAML node stream is not tightly associated with the XML text representation but rather the logical notion of a hierarchy of objects with various members set to various values.

Figure 2.4 Readers and writers working together to enable all sorts of transformations.

image

The ... parts of Figure 2.4 are important, as there can be a rich set of third-party readers and writers that enable a wide variety of transformations. Over the past few years, people have shared a number of converters that transform XAML to and from other file formats (although not yet based on these new APIs at the time of writing). These formats include more than 40 3D formats (Autodesk 3ds Max and Maya, AutoCAD DXF, NewTek LightWave, and so on), Adobe Illustrator/Photoshop/Flash/Fireworks, SVG, HTML 5 Canvas, Visio, PowerPoint, Windows Metafile (WMF), Enhanced Metafile (EMF), and even Visual Basic 6 forms!

Warning: The functionality in this section works best with non-WPF XAML!

There’s a reason this section is called “Fun with XAML Readers and Writers.” Sure, these classes are fun to work with, but you might have to limit your use of them to experimental tinkering for now. The current version of XamlObjectReader doesn’t support several aspects of WPF objects, so for WPF XAML serialization, you’ll have to stick with System.Windows.Markup.XamlWriter. If you’re using XAML for non-WPF purposes, then it should work great for you.

FAQ

image Why is XamlXmlReader better at reading a XAML file than a simple XmlReader? Isn’t XAML just XML?

XamlXmlReader does use XmlReader to do its work, but it provides two important features on top of the reading of XML:

• It abstracts away differences in XML representations that have equivalent meanings in XAML.

• It produces a XAML node stream that is compatible with any XAML writer and contains rich information not even present in the source XML.

The first point is crucial for reducing the amount of work needed to consume XAML. The following three chunks of XAML all express the same concept—a Button whose content property called Content is set to the string "OK":

image

These three snippets look very different to XmlReader but are made to look the same by XamlXmlReader. This is exactly what a XAML-consumption tool wants (unless the tool is doing something like enforcing style guidelines on the textual representation of XAML), and it takes considerable extra work. For example, XamlXmlReader can only know that that the first snippet is equivalent to the other two by examining the definition of Button and discovering that it has a content property named Content.

As for the second point, the rich information present in the XAML node stream provided by XamlXmlReader (or any XAML reader) is a result of combining the input data with the definitions of the types being referenced. For example, through XamlXmlReader, you can discover that Content is a content property, and its type is System.Object.

The Node Loop

Performing a transformation from one format to another involves reading XAML nodes from an appropriate reader and sending them to an appropriate writer. XamlReader and XamlWriter are designed to make this easy, enabling you to write a simple “node loop” that performs the necessary reading and writing from beginning to end. With a XAML reader called reader and a XAML writer called writer, here is what a simple node loop looks like:

image

What actually happens in this loop depends on the type of the reader and writer. The XAMLPAD2009 sample has the goal of reading XAML in XML format (stored in a string) and producing a live object graph that can be attached (and therefore rendered) inside its own user interface. Therefore, Listing 2.1 uses the simple node loop with XamlXmlReader and XamlObjectWriter to accomplish this. Most of the effort involves getting XamlXmlReader to read an XML string. The easiest way to do this is to create a System.IO.StringReader for the string which can be passed to XamlXmlReader (because StringReader is a TextReader).

Listing 2.1 A Simple Node Loop That Converts a XAML XML String to a Live Object Graph

image

The WPF schema context is passed to XamlObjectWriter to make it work better with WPF XAML. It enables a number of features and compatibility quirks that aren’t appropriate for general-purpose XAML processing.

Reading XAML

XAML readers expose a lot of useful information about the resultant XAML node stream, so you can do a whole lot more than just blindly write the nodes into some other form, such as morphing the content during the transformation.

The most important XamlReader property to inspect when writing a custom node loop is NodeType, which can be one of eight enumeration values:

StartObject—The reader is positioned at the start of an explicit object, such as an element’s start tag in XML or the beginning of a markup extension in a property value.

GetObject—The reader is positioned at the start of an implicit object, such as a collection whose items appear in XAML but not the collection itself (as seen with ListBox in the earlier “Collection Items” section).

EndObject—The reader is positioned at the end of an object (which was previously discovered via StartObject or GetObject). Every StartObject and GetObject node is matched with a corresponding EndObject node later in the stream.

StartMember—The reader is positioned at the start of an object’s member: a property (attached or not), an event (attached or not), or a XAML directive such as x:Key. Every member belongs to a parent object, so you won’t encounter a StartMember node without first encountering a StartObject or GetObject node. Note that in XML, it doesn’t matter whether the member is specified using property attribute syntax or property element syntax—it still shows up as a member, not an object.

EndMember—The reader is positioned at the end of a member (which was previously discovered via StartMember). Every StartMember node is matched with a corresponding EndMember node later in the stream.

Value—The reader is positioned at the start of a member’s value. Every value is associated with a member, so you won’t encounter a Value node without first encountering a StartMember node (and a StartObject or GetObject node before that).

NamespaceDeclaration—The reader is positioned at the declaration of an XML namespace (which associates the namespace value with a prefix). Note that these appear in the XAML node stream immediately before the StartObject node that “contains” these declarations. This might sounds surprising, but given that the namespace declarations provide context to even the root element, it’s valuable to have that context first.

None—The reader is positioned at something that is not a real node, such as the end of a file. This NodeType can be safely ignored.

XamlReader defines four important properties that enable you to extract the relevant data about any node: Type, Member, Value, and Namespace. The data that you can retrieve from these properties depends on the node type of its current position. For example, when NodeType is StartObject, Type is set to a XamlType instance, and the other three properties are null. When NodeType is StartMember, Member is set to a XamlMember instance, and the other three properties are null. When NodeType is Value, the Value property is the only one that is non-null, and when NodeType is NamespaceDeclaration, Namespace is the only non-null property.

In addition, all the XAML readers in the .NET Framework 4.0 (except for XamlObjectReader) implement an IXamlLineInfo interface that produces line number information when available. When the HasLineInfo property is true, you can retrieve row and column data from LineNumber and LinePosition properties, respectively.

FAQ

image What are these XamlType and XamlMember instances exposed by XAML readers?

These classes expose a XAML-specific form of .NET reflection.

XamlType wraps System.Type (which is available from XamlType’s UnderlyingType property), adding XAML-specific concepts such as content properties, attached properties, and much more. This layer of abstraction also enables XamlType to represent non-.NET types, if desired.

XamlMember effectively wraps System.Reflection.MemberInfo (which is available from XamlMember’s UnderlyingMember property when there actually is an underlying MemberInfo). It also adds XAML-specific concepts such as IsDirective and PreferredXamlNamespace properties.

To demonstrate what working with a XAML reader looks like in depth, Table 2.1 traces through the node stream produced by XamlXmlReader when reading the XAML content in Listing 2.2. The indenting of the XamlNodeType values illustrates the nesting of objects, members, and values.

Table 2.1 The XAML Node Stream Produced by XamlXmlReader When Reading Listing 2.2

image

image

Listing 2.2 Sample XAML Content to Demonstrate the Behavior of XamlXmlReader

image

Notice that all three ListBoxItem elements are represented identically in Table 2.1, as are the two Button elements, although it is possible to tell the difference between the use of Button’s Name property and the use of the x:Name XAML directive. (In the latter case, XamlMember is a derived XamlDirective type whose IsDirective property returns true.) Also notice that GetObject, EndMember, and EndObject are not accompanied with any additional information; relevant information must be derived from the rest of the node stream. Because of this, interesting transformations to XAML often involve maintaining your own stack with data related to objects and/or members.

Writing to Live Objects

The XAMLPAD2009 sample doesn’t convert XAML to live objects as-is; it makes a few modifications to the XAML content to ensure that a wider range of WPF XAML can be rendered successfully. Specifically, it makes two modifications to the content:

• It removes all event members, because unless the handler can be located, XamlObjectWriter would fail with an exception explaining, for example, “Failed to create a ‘Click’ from the text ‘button_Click’.” Note that XamlObjectWriter has a RootObjectInstance property that could be set to an object with appropriate event handlers, but stripping out the events is the easiest approach, and usually just fine for a XAML experimentation tool. It also removes x:Class because it’s not valid for loose XAML.

• It converts any Window element into a Page element instead. Chapter 7 covers these elements in depth, but the bottom line is that a Window element cannot be a child of another element, and XAMLPAD2009 always attempts to attach the root instance as a child of its own user interface. There are other ways to handle this (such as detecting when the root is a Window element and launching it on its own), but swapping one XAML node with another makes for an instructive sample.

Listing 2.3 shows the custom node loop that makes these two customizations while transforming the content from an XML string to live objects.

Listing 2.3 A Custom Node Loop That Converts a XAML XML String to a Live Object Graph with Modifications

image

Listing 2.3 leverages XamlReader’s Skip method to skip event members (IsEvent = true) and any x:Class members. (The latter is checked with help from the handy System.Xaml.XamlLanguage static class, which exposes all XamlDirectives and the built-in system XamlTypes as read-only properties for easy comparison.) When the reader is on a StartObject or StartMember node, Skip advances the stream to the node after the matching EndObject/EndMember (skipping any nested objects/members, which is exactly what we want). When the reader is on any other node type, calling Skip is equivalent to calling Read again: It advances to the next node.

For the Window/Page replacement, only the StartObject node needs to be swapped out. Recall that an EndObject node doesn’t have any data associated with it; its meaning depends on the rest of the node stream. So an EndObject node for Window can happily become an EndObject node for Page. This replacement doesn’t properly transfer Window’s members to the Page, however, because they are resolved on the Window by the reader before the node loop begins. The source code accompanying this book does the extra work of creating a new member on the Page for each applicable member set on the Window.

You’ve seen from Listings 2.1 and 2.3 that XamlObjectWriter.Result is set to the root object instance when the node loop is finished. More specifically, every time an EndObject node is successfully written, XamlObjectWriter.Result is set to the live object instance corresponding to that object. Because the last EndObject written to the node stream belongs to the root node, the final value of Result is the root.

Writing to XML

Writing WPF objects to XAML in XML form is a common activity. Because XamlObjectReader doesn’t currently support WPF objects, Listing 2.4 demonstrates converting from XML to XML by pairing up XamlXmlReader with XamlXmlWriter. This may sound nonsensical, but the combination produces a simple “XAML scrubber” that normalizes the input XML to produce consistently represented, consistently spaced XML with comments removed.

Listing 2.4 A “XAML Scrubber” That Normalizes the Input XML

image

Just about all the work is setting up the reader and writer. XamlXmlReader is constructed the same way as in the previous listing. XamlXmlWriter is constructed from an XmlWriter, which is constructed from a System.IO.StringWriter. (XmlWriter could alternatively be constructed with a StringBuilder.) The use of XmlWriter enables pretty printing (each element on a separate line with appropriate indenting) as well as the removal of an unnecessary XML declaration (<?xml version="1.0" encoding="utf-16"?>). If you don’t care about these things and are fine with all the content being emitted on the same line, you could directly give XamlXmlWriter the StringWriter (because it’s a TextWriter) rather than wrap it in the XmlWriter:

image

XamlServices

To minimize the amount of code you need to write, the most common uses for XAML readers and writers are packaged in a set of easy-to-use static methods in a class called System.Xaml.XamlServices. It has the following methods:

Load—Depending on the overload, you can give it a filename string, a Stream, a TextReader, an XmlReader, or a XamlReader, and it returns the root of the corresponding live object graph, like the older XamlReader.Load API. Internally, Load uses XamlXmlReader and XamlObjectWriter to do its work, as in Listing 2.1.

Parse—Like Load, Parse returns the root of a live object graph, but it accepts XAML content as a string for input. Internally, it creates a StringReader for the string, creates an XmlReader and then a XamlXmlReader so it can call Load. This makes Parse just like the ConvertXmlStringToObjectGraph method in Listing 2.1.

SaveSave takes an object as input and, depending on the overload, returns the content as a string, Stream, TextWriter, XmlWriter, or XamlWriter, or even saves the contents directly to a text file. Internally, Save uses XamlObjectReader and XamlXmlWriter (unless you pass in a different XamlWriter). It sets the XmlWriter’s Indent and OmitXmlDeclaration properties to true, just like in Listing 2.4.

TransformTransform performs a basic node loop with whatever reader and writer are passed in.

XamlServices.Transform is actually slightly more sophisticated than the simple node loop presented earlier. It preserves line number and line position information if both the reader and the writer support the appropriate interfaces to produce and consume it (IXamlLineInfo for the reader and IXamlLineInfoConsumer for the writer). Therefore, Transform effectively does the following:

image

Therefore, the node loop from Listing 2.1 could be replaced (and slightly enhanced) by replacing the node loop with a call to XamlServices.Transform, as shown in Listing 2.5. Of course, the whole ConvertXmlStringToObjectGraph method is unnecessary, as it is a duplication of XamlServices.Parse.

Listing 2.5 A Minor Simplification to Listing 2.1

image

Warning: Beware of XamlServices gotchas with WPF XAML!

You might expect that you could combine XamlServices.Parse and XamlServices.Save to implement the XAML scrubber from Listing 2.4 in an easy, albeit inefficient, manner:

image

image

This would be inefficient because internally the string goes through a XamlXmlReader to be written to live objects with a XamlObjectWriter (the root of which is returned by XamlServices.Parse), and then the hierarchy of objects is read by a XamlObjectReader before being written by a XamlXmlWriter into an XmlWriter to produce the final string. The intermediate step of transferring to live objects is problematic for more than just performance reasons; it requires special treatment in the face of certain XAML such as event handlers that need to be attached or an x:Class directive that needs to be resolved.

Even worse than these limitations, the code simply doesn’t work because XamlObjectWriter doesn’t currently support WPF objects. Instead, you could use the older XamlReader and XamlWriter:

image

Or, if you care about pretty printing:

image

But these approaches still suffer from the problems inherent to converting the XAML to live objects as an intermediate step.

Tip

The Microsoft XAML Toolkit, available from http://code.msdn.microsoft.com/XAML, builds on System.Xaml and provides several compelling features, such as XAML integration into the FxCop tool and a XAML Document Object Model (DOM). The XAML DOM is a LINQ-friendly set of APIs that enables even easier inspection and modification of XAML content compared what the readers and writers in this chapter enable. The toolkit also includes additional schema contexts—SilverlightSchemaContext for Silverlight XAML and UISchemaContext that provides a common abstraction for WPF XAML and Silverlight XAML.

XAML Keywords

The XAML language namespace (http://schemas.microsoft.com/winfx/2006/xaml) defines a handful of keywords that must be treated specially by any XAML compiler or parser. They mostly control aspects of how elements get exposed to procedural code, but several are useful even without any procedural code. You’ve already seen some of them (such as Key, Name, Class, Subclass, and Code), but Table 2.2 lists them all. They are listed with the conventional x prefix because that is how they usually appear in XAML and in documentation.

Table 2.2 Keywords in the XAML Language Namespace, Assuming the Conventional x Namespace Prefix

image

image

image

image

Table 2.3 contains additional items in the XAML language namespace that can be confused as keywords but are actually just markup extensions (real .NET classes in the System.Windows.Markup namespace). Each class’s Extension suffix is omitted from the table because the classes are typically used without the suffix.

Table 2.3 Markup Extensions in the XAML Language Namespace, Assuming the Conventional x Namespace Prefix

image

Summary

You have now seen how XAML fits in with WPF and, most importantly, you now have the information needed to translate most XAML examples into a language such as C# and vice versa. However, because type converters and markup extensions are “black boxes,” a straightforward translation is not always going to be obvious. That said, invoking a type converter directly from procedural code is always an option if you can’t figure out the conversion that the type converter is doing internally! (Many classes with corresponding type converters even expose a static Parse method that does the same work, for the sake of simpler procedural code.)

I love the fact that simple concepts that could have been treated specially by XAML (such as null or a named reference) are expressed using the same markup extension mechanism used by third parties. This keeps the XAML language as simple as possible, and it ensures that the extensibility mechanism works really well.

As you proceed further with WPF, you might find that some WPF APIs can be a little clunky from procedural code because their design is often optimized for XAML use. For example, WPF exposes many small building blocks (enabling the rich composition described in the previous chapter), so a WPF application generally must create far more objects manually than, say, a Windows Forms application. Besides the fact the XAML excels at expressing deep hierarchies of objects concisely, the WPF team spent more time implementing features to effectively hide intermediate objects in XAML (such as type converters) rather than features to hide them from procedural code (such as constructors that create inner objects on your behalf).

Most people understand the benefit of WPF having the separate declarative model provided by XAML, but some lament XML as the choice of format. The following sections are two common complaints and my attempt to debunk them.

Complaint 1: XML Is Too Verbose to Type

This is true: Almost nobody enjoys typing lots of XML, but that’s where tools come in. Tools such as IntelliSense and visual designers can spare you from typing a single angle bracket! The transparent and well-specified nature of XML enables you to easily integrate new tools into the development process (creating a XAML exporter for your favorite tool, for example) and also enables easy hand-tweaking or troubleshooting.

In some areas of WPF—complicated paths and shapes, 3D models, and so on—typing XAML by hand isn’t even practical. In fact, the trend from when XAML was first introduced in beta form has been to remove some of the handy human-typable shortcuts in favor of a more robust and extensible format that can be supported well by tools. But I still believe that being familiar with XAML and seeing the WPF APIs through both procedural and declarative perspectives is the best way to learn the technology. It’s like understanding how HTML works without relying on a visual tool.

Complaint 2: XML-Based Systems Have Poor Performance

XML is about interoperability, not about an efficient representation of data. So, why should most WPF applications be saddled with a bunch of data that is relatively large and slow to parse?

The good news is that in a normal WPF scenario, XAML is compiled into BAML, so you don’t pay the full penalties of size and parsing performance at runtime. BAML is both smaller in size than the original XAML and optimized for efficient use at runtime. Performance pitfalls from XML are therefore limited to development time, which is when the benefits of XML are needed the most.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset