Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

1032 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

Using XamlPad, you are able to author XAML markup in the pane mounted at the bottom of the window and view the output above. When you first start this tool, you will find little more than an empty <Page> declaration, which is used to contain markup for an XBAP application:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Grid>

<!-- Add your XAML here! -->

</Grid>

</Page>

Although XamlPad does not allow you to view markup for a <Window> element directly within the XamlPad view window, you are free to change <Page> to <Window> and press the F5 key to launch a stand-alone window to display your content. Furthermore, be aware that the markup you enter in a <Page> or <Window> element is identical.

Note XamlPad does not allow you author any markup that entails code compilation. This includes defining a Class attribute (for specifying a code file), using <Code> elements, or using any XMAL keywords that also entail code compilation (such as FieldModifier or ClassModifier). Any attempt to do so will result in a markup error.

As you are authoring markup with XamlPad, you will notice a lack of IntelliSense. However, clicking the Show Visual Tree button opens a UI that mimics the Visual Studio Properties window to help you visualize the structure of your XAML markup. Sadly, the Visual Tree window cannot (currently) be used to change the XAML itself; it is a read-only view of the XAML markup.

Also be aware that XamlPad currently has no way in which you can save individual *.xaml files; markup is automatically saved to XamlPad_Saved.xaml and will be displayed the next time you load the tool (feel free to copy the markup and paste it into your current application, however).

XAML Namespaces and XAML Keywords

As you have already seen in this chapter’s earlier examples, the root element of a WPF-centric XAML file (such as a <Window>, <Page>, or <Application> definition) is typically defined to make reference to two XML namespaces:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Grid>

</Grid>

</Page>

The first XML namespace, http://schemas.microsoft.com/winfx/2006/xaml/presentation, maps a slew of WPF-centric namespaces for use by the current *.xaml file (System.Windows,

System.Windows.Controls, System.Windows.Data, System.Windows.Ink, System.Windows.Media,

System.Windows.Navigation, etc.). This one-to-many mapping is actually hard-coded within the WPF assemblies (WindowsBase.dll, PresentationCore.dll, and PresentationFramework.dll) using the assembly-level [XmlnsDefinition] attribute. If you load these WPF assemblies into reflector.exe, you can view these mappings firsthand (see Figure 28-11).

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1033

Figure 28-11. The http://schemas.microsoft.com/winfx/2006/xaml/presentation namespace maps to the core WPF namespaces.

The second XML namespace, http://schemas.microsoft.com/winfx/2006/xaml, is used to include XAML-specific keywords as well as a subset of types within the System.Windows.Markup namespace. A well-formed XML document must define a root element that designates a single XML namespace as the primary namespace, which typically is the namespace that contains the most commonly used items. If a root element requires the inclusion of additional secondary namespaces (as seen here), they must be defined using a unique prefix (to resolve any possible name clashes). As a convention, the prefix is simply x; however, this can be any unique token you require, such as XamlSpecificStuff:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:XamlSpecificStuff="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid>

</Grid>

</Page>

The obvious downside of defining wordy XML namespace prefixes is you would be required to type XamlSpecificStuff each time your XAML file needs to refer to one of the types defined in the namespace in question. For example, one of the items within http://schemas.microsoft.com/ winfx/2006/xaml is the XAML keyword Code, which as you have seen allows you to embed C# code within an XAML document. Another XAML keyword is Class, which allows you to define the name of the generated C# class type.

If we were to change the definition of the MyApp XAML definition created earlier in this chapter to make use of this more verbose XML namespace prefix, we would now be required to author the following:

<Application XamlSpecificStuff:Class="SimpleXamlApp.MyApp" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:XamlSpecificStuff ="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" Exit ="AppExit">

< XamlSpecificStuff:Code> <![CDATA[

private void AppExit(object sender, ExitEventArgs e)

{

MessageBox.Show("App has exited");

}

]]> </XamlSpecificStuff:Code>

</Application>

1034 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

Given that XamlSpecificStuff requires many additional keystrokes, let’s just stick with x. In any case, beyond the Class and Code keywords, including the http://schemas.microsoft.com/winfx/ 2006/xaml XML namespace also provides access to additional XAML keywords (and members of the System.Windows.Markup namespace), the core of which are shown in Table 28-9.

Table 28-9. XAML Keywords

XAML Keyword

Meaning in Life

Array

Represents a .NET array type in XAML.

ClassModifier

Allows you to define the visibility of the class type (internal or public) denoted

 

by the Class keyword.

DynamicResource

Allows you to make reference to a WPF resource that should be monitored for

 

changes.

FieldModifier

Allows you to define the visibility of a type member (internal, public, private,

 

or protected) for any named subelement of the root (e.g., a <Button> within a

 

<Window> element). A “named element” is defined using the Name XAML

 

keyword.

Key

Allows you to establish a key value for an XAML item that will be placed into a

 

dictionary element.

Name

Allows you to specify the generated C# name of a given XAML element.

Null

Represents a null reference.

Static

Allows you to make reference to a static member of a type.

StaticResource

Allows you to make reference to a WPF resource that should not be monitored

 

for changes.

Type

The XAML equivalent of the C# typeof operator (it will yield a System.Type

 

based on the supplied name).

TypeArguments

Allows you to establish an element as a generic type with a specific type

 

parameter (e.g., List<int> vs. List<bool>).

 

 

You will see many of these keywords in action where required; however, by way of a simple example, consider the following XAML <Window> definition that makes use of the ClassModifier and FieldModifier keywords, as well as Name and Class (remember that xamlpad.exe will not allow you to make use of any XAML keyword that entails code compilation, such as Code, FieldModifier, or

ClassModifier):

<!-- This class will now be internal.

If using a code file, the partial class must also be defined as internal! -->

<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- This button will be public in the *.g.cs file -->

<Button x:Name ="myButton" x:FieldModifier ="public"> OK

</Button>

</Window>

By default, all C#/XAML type definitions are public, while members default to internal. However, based on our XAML definition, the resulting autogenerated file contains an internal class type with a public Button type:

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1035

internal partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector

{

public System.Windows.Controls.Button myButton;

...

}

XAML Elements and XAML Attributes

Once you have established your root element and any required XML namespaces, your next task is to populate the root with a child element. As mentioned, in a real-world WPF application, the child will be one of the panel types, which contains in turn any number of additional UI elements that describe the user interface. The next chapter examines these panel types in detail, so for the time being assume that our <Window> type will contain a single Button element.

As you have already seen over the course of this chapter, XAML elements map to a class or structure type within a given .NET namespace, while the attributes within the opening element tag map to properties or events of the type (you cannot reference the methods of a type via an XAML attribute). Thus, when you author code such as the following:

<Window x:Class="MyWPFApp.MainWindow" x:ClassModifier ="internal" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<!-- This assumes you have a method named myButton_Click in your code file! -->

<Button x:Name ="myButton" x:FieldModifier ="public"

Height ="50" Width ="100" Click ="myButton_Click">

OK </Button>

</Window>

you have effectively authored a Button that could be expressed in code as so:

Button myButton = new Button(); myButton.Height = 50; myButton.Width = 100; myButton.Content = "OK";

myButton.Click += new RoutedEventHandler(myButton_Click);

Given your work thus far in the chapter, this mapping may seem straightforward; however, consider the assignment of the button’s content. Recall that many WPF controls derive from ContentControl. By doing so, they are able to contain any number of internal items (such as a Button with a ScrollBar). Here, the Content property was implicitly set due to the fact that we placed the text "OK" within the opening and closing element. If we wish, we could explicitly set the Content property as follows:

<Button x:Name ="myButton"

Height ="50" Width ="100" Content = "OK"> </Button>

At this point, the act of setting the Content property implicitly or explicitly may seem to be nothing more than a personal preference. The story becomes more interesting when you consider how you would use XAML to assign a Button’s content to be an object other than a simple string (a graphical rendering, a ScrollBar or TextBox, etc.). As mentioned earlier in this chapter, the solution is to use property-element syntax.

1036 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

Understanding XAML Property-Element Syntax

Property-element syntax allows you to assign complex objects to a property. Here is an XAML description for the “scrollbar in a button” scenario that sets the Content property using propertyelement syntax:

<Button x:Name ="myButton" Height ="100" Width ="100"> <Button.Content>

<ScrollBar Height = "50" Width = "20"/> </Button.Content>

</Button>

Notice that in this case, we have made use of a nested element named <Button.Content> to define the ScrollBar type. Property-element syntax always breaks down to the pattern <TypeName. PropertyName>; obviously the type in this case is <Button> while the property is Content. Figure 28-12 shows the output as seen in xamlpad.exe.

Figure 28-12. Property-element syntax allows you to assign complex objects to properties.

Also recall that the child element of a ContentControl-derived type will automatically be used to set the Content property, therefore the following definition is also legal:

<Button x:Name ="myButton" Height ="100" Width ="100"> <ScrollBar Height = "50" Width = "20"/>

</Button>

Property-element syntax is not limited to setting the Content property. Rather, this XAML syntax can be used whenever you need to set a complex object to a type property. Consider, for example, the Background property of the Button type. This property can be set on any Brush type found within the WPF APIs. If you need a solid color brush type, the following markup is all that is required, as the string value assigned to properties requiring a Brush-derived type (such as Background) is converted into a brush type automatically:

<!-- Here, "Green" maps to Brushes.Green -->

<Button x:Name ="myButton" Height ="100" Width ="100" Background ="Green"> <Button.Content>

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1037

<ScrollBar Height = "50" Width = "20"/> </Button.Content>

</Button>

However, if you need a more elaborate brush (such as a LinearGradientBrush), name/value syntax will not suffice. Considering that LinearGradientBrush is a full-blown class type, we must make use of property-element syntax to pass in startup values to the type:

<Button x:Name ="myButton" Height ="100" Width ="100"> <Button.Content>

<ScrollBar Height = "50" Width = "20"/> </Button.Content>

<Button.Background>

<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="Blue" Offset="0" /> <GradientStop Color="Yellow" Offset="0.25" /> <GradientStop Color="Green" Offset="0.75" /> <GradientStop Color ="Red" Offset="0.50" />

</LinearGradientBrush>

</Button.Background>

</Button>

Don’t concern yourself with the configuration of the LinearGradientBrush type for the time being (Chapter 30 addresses WPF’s graphical rendering services). Simply notice that we have used property-element syntax to establish the Content and Background property of the Button type. Figure 28-13 shows the rendering of this rather fancy button.

Figure 28-13. A very fancy button type

While property-element syntax is most often used to assign complex objects (such as LinearGradientBrush) to property values, it is permissible to make use of simple string values as well:

<Button x:Name ="myButton" Height ="100" Width ="100"> <Button.Content>

<ScrollBar Height = "50" Width = "20"/> </Button.Content>

<Button.Background>

Pink

</Button.Background>

</Button>

In this case, you have really gained nothing. Rather, you have just complicated the process, as you could simply have typed the following:

<Button x:Name ="myButton" Height ="100" Width ="100" Background = "Pink"> <Button.Content>

<ScrollBar Height = "50" Width = "20"/> </Button.Content>

</Button>

1038 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

Understanding XAML Attached Properties

In addition to property-element syntax, XAML defines syntax used to define an attached property. While attached properties have many uses, one purpose of an attached property is to allow different child elements to specify unique values for a property that is actually defined in a parent element. The most common use of attached property syntax is to position UI elements within one of the WPF panel types (Grid, DockPanel, etc.). The next chapter dives into these panels in some detail. For the time being, here is an example of attached-property syntax:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <DockPanel LastChildFill ="True">

<!-- Dock items to the panel using attached properties -->

<Label DockPanel.Dock ="Top" Name="lblInstruction" FontSize="15">Enter Car Information</Label>

<Label DockPanel.Dock ="Left" Name="lblMake">Make</Label> <Label DockPanel.Dock ="Right" Name="lblColor">Color</Label>

<Label DockPanel.Dock ="Bottom" Name="lblPetName">Pet Name</Label> <Button Name="btnOK">OK</Button>

</DockPanel>

</Page>

Here, we have defined a DockPanel type that contains four Label types docked within the container. Notice the format of this particular attached property syntax is <ParentType.ParentProperty> (e.g., DockPanel.Dock). Note that the Button type does not specify a docking area; however, it will take over the remaining area in the DockPanel, giving the assignment of the LastChildFill property in the opening <DockPanel> definition.

There are a few items to be aware of regarding attached properties. First and foremost, this is not an all-purpose syntax that can be applied to any element of any parent. For example, the following XAML cannot be parsed without error:

<!-- Set Height property on Button via attached property? -->

<Button x:Name ="myButton" Width ="100"> <Button.Content>

<ScrollBar Button.Height = "100" Height = "50" Width = "20"/> </Button.Content>

<Button.Background> Pink

</Button.Background>

</Button>

In reality, attached properties are a specialized form of a WPF-specific concept termed a dependency property. In a nutshell, dependency properties allow the value of a field to be computed based on multiple inputs. Dependency properties, and therefore attached properties, need to “register” which properties can be set by which objects (which has not been done for the ScrollBar/Button scenario just shown).

WPF uses the dependency property mechanism under the hood for several technologies such as data binding, styles and themes, and animation services. As well, a dependency property can be implemented to provide self-contained validation, default values, and a callback mechanism, and it provides a way to establish property values based on runtime information.

The odd thing about dependency properties is the fact that setting a dependency property value looks no different from setting a “normal” .NET property. Therefore, in most cases, you will be blissfully unaware that you have set a dependency property value.

However, the manner in which dependency properties are implemented behind the scenes is quite different indeed. For the vast majority of your WPF applications, you will not need to author

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1039

custom dependency properties. The only time this may become a common task is when you are in the position of building custom WFP controls, which again is not a common activity in the first place given the advent of XAML. You will explore dependency properties in a bit more detail in Chapter 29.

Understanding XAML Type Converters

For all practical purposes, when you are assigning values to attributes (e.g., Background = "Pink") or implicitly setting content within the scope of an opening and closing element (e.g., <Button>OK</ Button>), you can simply assume the values to be string data. However, if you think this through, it clearly could not be the case. Consider, for example, the definition of the Background property of the Button type (which we inherited from the Control base class):

// The System.Windows.Controls.Control.Background property. public Brush Background

{

...

}

As you can see, this property is wrapping a Brush type, not a System.String! This begs the question, what is transforming "Pink" into (in this case) a SolidColorBrush object with RGB values that equal the color pink? Here’s another example. Consider the following XAML definition of a purple ellipse of a given size:

<Ellipse Fill = "Purple" Width = "100.5" Height = "87.4"> </Ellipse>

If you were to look at the definition of the Width and Height properties of the Ellipse type, you would find they are prototyped to operate on doubles, not strings.

Behind the scenes, XAML parsers make use of various type converters to transform this string data into the correct underlying object. For example, the value "Pink", when assigned to a property prototyped to operate on brush types, makes use of the ColorConverter and BrushConverter types. Numerous other converters exist as well: SizeConverter (used to set the Width and Height properties of the previous Ellipse), RectConverter, VectorConverter, and so on.

Regardless of their names, all type converters derive from the System.ComponentModel. TypeConverter base class. This type defines a number of virtual methods such as CanConvertTo(), ConvertTo(), CanConvertFrom(), and ConvertFrom(), which can be overridden by a derived type to account for the underlying translation.

For the most part, you do not need to know which type converter is mapping your XAML string data to the correct underlying object. At the very least, simply understand that they are used transparently in the background to simplify XAML definitions.

Note Although XAML itself is a relativity new technology, the concept of type converters has existed since the release of the .NET platform. Windows Forms and GDI+ make use of various converters behind the scenes. For example, the GDI+ System.Drawing namespace defines a type converter named FontConverter, which can map the string "Wingdings" into a Font object using the Wingdings font face.

Understanding XAML Markup Extensions

Type converters are interesting constructs in that there is no physical evidence that you are interacting with them at the level of XAML. Rather, type converters are used internally behind the scenes

1040 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

when an *.xaml file is processed. In contrast, XAML also supports markup extensions. Like a type converter, markup extensions allow you to transform a simple markup value into a runtime object. The difference, however, is that markup extensions have a very specific XAML syntax.

Given that type converters and markup extensions appear to serve an identical purpose, you might wonder why we have two approaches to generate type definitions. Simply put, markup extensions allow for a greater level of flexibility than type converters and provide a way to cleanly extend the grammar of XAML with new functionality.

Using markup extensions, you could assign the value of a property to the return value of a static property on another type, declare an array of data via markup, or obtain type information. In fact, a subset of XAML keywords (such as Array, Null, Static, and Type) are markup extensions in disguise. Like type converters, a markup extension is represented internally as a class that derives from MarkupExtension (as a naming convention, all types that subclass MarkupExtension take an

Extension suffix).

To see a markup extension in action, assume you wish to set the Content property for a set of Labels to display information regarding the machine your application is executing on using static members of System.Environment. Here is the complete markup, with a discussion to follow:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:CorLib="clr-namespace:System;assembly=mscorlib">

<StackPanel>

<Label Content ="{x:Static CorLib:Environment.MachineName}"/> <Label Content ="{x:Static CorLib:Environment.OSVersion}"/> <Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>

</StackPanel>

</Page>

First of all, notice that the <Page> definition has a new XML namespace declaration, which we have given the namespace prefix of CorLib (the name of this prefix, like any XML namespace prefix, is arbitrary). The value assigned to this XML namespace is unique, however, as we are making use of a registered token named clr-namespace (which allows us to point to a .NET namespace that contains the type definition) and another token named assembly (which represents the friendly name of the assembly containing the namespace).

With this XML namespace established, notice how each of the Label types can invoke a static member of the Environment type via the Static markup extension. As you can see, markup extensions are always sandwiched between curly brackets. In its simplest form, the markup extension takes two values: the name of the markup extension (Static in this case) followed by the value to assign it (such as CorLib:System.Environment.OSVersion).

<!-- Using the 'Static' markup extension to set the Content property to the value of a static property. -->

<Label Content ="{x:Static CorLib:Environment.OSVersion}"/>

Here is another example. Assume you wish to obtain the fully qualified name of various types to assign the Content property to another set of Label types. In this case, you can make use of the baked-in Type markup extension:

<Page

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:CorLib="clr-namespace:System;assembly=mscorlib">

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1041

<StackPanel>

<Label Content ="{x:Static CorLib:Environment.MachineName}"/> <Label Content ="{x:Static CorLib:Environment.OSVersion}"/> <Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>

<Label Content ="{x:Type Label}" /> <Label Content ="{x:Type Page}" />

<Label Content ="{x:Type CorLib:Boolean}" /> <Label Content ="{x:Type x:TypeExtension}" />

</StackPanel>

</Page>

Here you are obtaining the fully qualified names of the WPF Label type, the Button type, as well as the Boolean data type within mscorlib.dll and, just for good measure, the fully qualified name of the Type markup extension itself. If you were to view this page within xamlpad.exe, you would find something like Figure 28-14.

Figure 28-14. Using markup extensions to set properties to values of static members and obtain type information

A Preview of Resources and Data Binding

To wrap up our introductory look at the syntax of XAML, this final example will not only illustrate using the Array markup extension (represented by the ArrayExtension class type), but also show some simple declarative data binding and preview the concept of WPF resources. The Array markup extension allows you to assign an array of data to a given property. When using XAML to define such an array, we do so by making use of the Type markup extension to establish what kind of array we are creating (array of strings, array of bitmaps, etc.). Consider the following <Page> definition: