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

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

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

1022 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

While tools can generate a good deal of XAML on your behalf, it is important for you to understand the basic workings of XAML syntax and how this markup is eventually transformed into a valid .NET assembly. To illustrate XAML in action, in our next example we’ll build a WPF application using nothing more than a pair of *.xaml files.

Defining MainWindow in XAML

Our first Window-derived class (MainWindow) was defined in C# as a class type that extends the System.Windows.Window base class. This class contains a single Button type that calls a registered event handler when clicked. Defining this same Window type in the grammar of XAML can be achieved as so (assume this markup has been defined in a file named MainWindow.xaml):

<!--

Here is our Window definition --

>

<Window x:Class="SimpleXamlApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="My Xaml App" Height="200" Width="300" WindowStartupLocation ="CenterScreen">

<!--Set the content of this window --

>

<Button Width="133" Height="24" Name="btnExitApp" Click ="btnExitApp_Clicked"> Exit Application

</Button>

<!--The implementation of our button's Click event handler! -->

<x:Code>

<![CDATA[

private void btnExitApp_Clicked(object sender, RoutedEventArgs e)

{

// Get a handle to the current app and shut it down. Application.Current.Shutdown();

}

]]>

</x:Code>

</Window>

First of all, notice that the root element, <Window>, defines the name of the derived type via the Class attribute. The x prefix is used to denote that this attribute is defined within the XAML-centric XML namespace, http://schemas.microsoft.com/winfx/2006/xaml (more details on these XML namespaces later in this chapter). Within the scope of the opening <Window> element we have specified values for the Title, Height, Width, and WindowsStartupLocation attributes, which as you can see are a direct mapping to properties of the same name supported by the System.Windows.Window type.

Next up, notice that within the scope of the window’s definition, we have authored markup to describe the look and feel of the Button instance, which will be used to implicitly set the Content property of the window. Beyond setting up the variable name and its overall dimensions, we have also handled the Click event of the Button type by assigning the method to delegate to when the Click event occurs.

The final aspect of this XAML file is the <Code> element, which allows us to author event handlers and other methods of this class directly within an *.xaml file. As a safety measure, the code itself is wrapped within a CDATA scope, to prevent XML parsers from attempting to directly interpret the data (although this is not strictly required for the current example).

It is important to point out that authoring functionality within a <Code> element is not recommended. Although this “single-file approach” isolates all the action to one location, inline code does

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1023

not provide us with a clear separation of concerns between UI markup and programming logic. In most WPF applications, “real code” will be found within an associated C# code file (which we will do eventually).

Defining the Application Object in XAML

Remember that XAML can be used to define in markup any nonabstract .NET class that supports a default constructor. Given this, we could most certainly define our application object in markup as well. Consider the following content within a new file, MyApp.xaml:

<!-- The Main() method seems to be missing! However, the StartupUri attribute is the functional equivalent -->

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

</Application>

Here, you might agree, the mapping between the Application-derived C# class type and its XAML description is not as clear-cut as was the case for our MainWindow’s XAML definition. Specifically, there does not seem to be any trace of a Main() method. Given that any .NET executable must have a program entry point, you are correct to assume it is generated at compile time, based in part on the StartupUrl property. The assigned *.xaml file will be used to determine which Window- derived class to create when this application starts up.

Although the Main() method is automatically created at compile time, we are free to use the <Code> element to establish our Exit event handler if we so choose, as follows (notice this method is no longer static, as it will be translated into an instance-level member in the MyApp class):

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

<x:Code>

<![CDATA[

private void AppExit(object sender, ExitEventArgs e)

{

MessageBox.Show("App has exited");

}

]]>

</x:Code>

</Application>

Processing the XAML Files via msbuild.exe

At this point, we are ready to transform our markup into a valid .NET assembly. When doing so, we cannot make direct use of the C# compiler and a response file. To date, the C# compiler does not have a direct understanding of XAML markup. However, the msbuild.exe command-line utility does understand how to transform XAML into C# code and compile this code on the fly when it is informed of the correct *.targets files.

msbuild.exe is a tool that allows you to define complex build scripts via (surprise, surprise) an XML grammar. One interesting aspect of these XML definitions is that they are the same format as Visual Studio *.csproj files. Given this, we are able to define a single file for automated commandline builds as well as a Visual Studio 2008 project. Consider the following minimalist build file,

SimpleXamlApp.csproj:

1024 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup>

<RootNamespace>SimpleXamlApp</RootNamespace>

<AssemblyName>SimpleXamlApp</AssemblyName>

<OutputType>winexe</OutputType>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" /> <Reference Include="WindowsBase" /> <Reference Include="PresentationCore" />

<Reference Include="PresentationFramework" /> </ItemGroup>

<ItemGroup>

<ApplicationDefinition Include="MyApp.xaml" /> <Page Include="MainWindow.xaml" />

</ItemGroup>

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />

</Project>

Here, the <PropertyGroup> element is used to specify some basic aspects of the build, such as the root namespace, the name of the resulting assembly, and the output type (the equivalent of the

/target:winexe option of csc.exe).

The first <ItemGroup> specifies the set of external assemblies to reference with the current build, which as you can see are the core WPF assemblies examined earlier in this chapter. The second <ItemGroup> is much more interesting. Notice that the <ApplicationDefinition> element’s Include attribute is assigned to the *.xaml file that defines our application object. The <Page>’s Include element can be used to list each of the remaining *.xaml files that define the windows (and pages, which are often used when building XAML browser applications) processed by the application object.

However, the “magic” of this *.csproj file is the final <Import> subelements. Notice that our build script is referencing two *.targets files, each of which contains numerous other instructions used during the build process. The Microsoft.WinFX.targets file contains the necessary build settings to transform the XAML definitions into equivalent C# code files, while Microsoft.CSharp. Targets contains data to interact with the C# compiler itself.

Note A full examination of the msbuild.exe utility is beyond the scope of this text. If you’d like to learn more, perform a search for the topic “MSBuild” in the .NET Framework 3.5 SDK documentation.

At this point, we can pass our SimpleXamlApp.csproj file into msbuild.exe for processing:

msbuild SimpleXamlApp.csproj

Once the build has completed, you should be able to find your assembly within the generated \bin\Debug folder. At this point, you can launch your WPF application as expected. As you may agree, it is quite bizarre to generate valid .NET assemblies by authoring a few lines of markup. However, to be sure, if you open SimpleXamlApp.exe in ildasm.exe, you can see that (somehow) your XAML has been transmogrified into an executable application (see Figure 28-6).

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1025

Figure 28-6. Transforming markup into a .NET assembly? Interesting . . .

Transforming Markup into a .NET Assembly

To understand exactly how our markup was transformed into a .NET assembly, we need to dig a bit deeper into the msbuild.exe process and examine a number of compiler-generated files, including a particular binary resource embedded within the assembly at compile time.

Mapping XAML to C# Code

As mentioned, the *.targets files specified in an MSBuild script define numerous instructions to translate XAML elements into C# code for compilation. When msbuild.exe processed our *.csproj file, it produced two files with the form *.g.cs (where g denotes autogenerated), which were saved into the \obj\Debug directory. Based on the names of our *.xaml file names, the C# files in question are MainWindow.g.cs and MyApp.g.cs.

If you open the MainWindow.g.cs file, you will find your class extends the Window base class and contains the btnExitApp_Clicked() method as expected. Also, this class defines a member variable of type System.Windows.Controls.Button. Strangely enough, there does not appear to be any code that establishes the property settings for the Button or Window type (Height, Width, Title, etc.). This part of the mystery will become clear in just a moment.

Finally, note that this class defines a private member variable of type bool (named _contentLoaded), which was not directly accounted for in the XAML markup. Here is a partial definition of the generated MainWindow type:

public partial class MainWindow :

System.Windows.Window, System.Windows.Markup.IComponentConnector

{

internal System.Windows.Controls.Button btnExitApp;

// This member variable will be explained soon enough.

1026 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

private bool _contentLoaded;

private void btnExitApp_Clicked(object sender, RoutedEventArgs e)

{

// Get a handle to the current application and shut it down. Application.Current.Shutdown();

}

...

}

This Windows-derived class also explicitly implements the WPF IComponentConnector interface defined in the System.Windows.Markup namespace. This interface defines a single method, Connect(), which has been implemented to rig up the event logic as specified within the original

MainWindow.xaml file:

void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target)

{

switch (connectionId)

{

case 1:

this.btnExitApp = ((System.Windows.Controls.Button)(target)); this.btnExitApp.Click += new

System.Windows.RoutedEventHandler(this.btnExitApp_Clicked); return;

}

this._contentLoaded = true;

}

Finally, the MainWindow class also implements a method named InitializeComponent(). This method ultimately resolves the location of an embedded resource within the assembly, given the name of the original *.xaml file. Once the resource is located, it is loaded into the current application object via a call to Application.LoadComponent(). Finally, the private bool member variable (mentioned previously) is set to true, to ensure the requested resource is loaded exactly once during the lifetime of this application:

public void InitializeComponent() { if (_contentLoaded) {

return;

}

_contentLoaded = true;

System.Uri resourceLocater = new System.Uri("/SimpleXamlApp;component/mainwindow.xaml", System.UriKind.RelativeOrAbsolute);

System.Windows.Application.LoadComponent(this, resourceLocater);

}

At this point, the question becomes, what exactly is this embedded resource?

The Role of BAML

When msbuild.exe processed our *.csproj file, it generated a file with a *.baml file extension, which is named based on the initial MainWindow.xaml file. As you might have guessed from the name, Binary Application Markup Language (BAML) is a binary representation of XAML. This *.baml file is embedded as a resource (via a generated *.g.resources file) into the compiled assembly. Using BAML, WPF assemblies contain within them their complete XAML definition (in a much more

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1027

compact format). You can verify this for yourself by opening your assembly using reflector.exe, as shown in Figure 28-7.

Figure 28-7. Viewing the embedded *.baml resource via Lutz Roeder’s .NET Reflector

The call to Application.LoadComponent() reads the embedded BAML resource and populates the tree of defined objects with their correct state (again, such as the window’s Height and Width properties). In fact, if you open the *.baml or *.g.resources file via Visual Studio, you can see traces of the initial XAML attributes. As an example, Figure 28-8 highlights the StartupLocation. CenterScreen property.

Figure 28-8. Behold the BAML!

The final piece of the autogenerated code puzzle occurs in the MyApp.g.cs file. Here we see our Application-derived class with a proper Main() entry point method. The implementation of this method calls InitializeComponent() on the Application-derived type, which in turn sets the

1028 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

StartupUri property, allowing each of the objects to establish its correct property settings based on the binary XAML definition.

namespace SimpleXamlApp

{

public partial class MyApp : System.Windows.Application

{

void AppExit(object sender, ExitEventArgs e)

{

MessageBox.Show("App has exited");

}

[System.Diagnostics.DebuggerNonUserCodeAttribute()] public void InitializeComponent() {

this.Exit += new System.Windows.ExitEventHandler(this.AppExit); this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative);

}

[System.STAThreadAttribute()]

[System.Diagnostics.DebuggerNonUserCodeAttribute()] public static void Main() {

SimpleXamlApp.MyApp app = new SimpleXamlApp.MyApp(); app.InitializeComponent();

app.Run();

}

}

}

XAML-to-Assembly Process Summary

Whew! So, at this point we have created a full-blown .NET assembly using nothing but three XML documents (one of which was used by the msbuild.exe utility). As you have seen, msbuild.exe leverages auxiliary settings defined within the *.targets file to process the XAML files (and generate the *.baml) for the build process. While these gory details happen behind the scenes, Figure 28-9 illustrates the overall picture regarding the compile-time processing of *.xaml files.

 

 

 

 

 

 

 

Output to \Obj\Debug Directory

MainWindow.xaml

msbuild.exe

MainWindow.g.cs

and Required

 

 

MyApp.xmal

 

 

My App.g.cs

 

 

C# and WPF

*.csproj

 

 

Targets

 

 

 

 

 

MainWindow.baml

 

 

 

 

 

 

 

 

 

 

 

 

 

 

SimpleXamlApp.g.resources

SimpleXamlApp.exe

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

C# Compiler

 

 

 

 

 

 

 

Embedded BAML

 

 

 

 

 

– Compile C# Files

 

 

 

 

 

Resource

 

 

 

 

– Embed *.g.resources as Resource

Figure 28-9. The XAML-to-assembly compile-time process

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1029

It is also important to point out that once the compiler has processed all of your *.xaml files in order to build the related C# code and binary resource, they are technically no longer required (and would never need to be shipped along with your executable). However, as shown at the end of this chapter, it is possible to dynamically create a Window object by reading a *.xaml file programmatically. In this case, the physical *.xaml file would indeed need to be shipped with the application itself.

Source Code The SimpleXamlApp project can be found under the Chapter 28 subdirectory.

Separation of Concerns Using Code-Behind Files

Before we truly begin digging into the details of XAML, we have one final aspect of the basic programming model to address: the separation of concerns. Recall that one of the major motivations for WPF was to separate UI content from programming logic, which our current examples have not done.

Rather than directly embedding our event handlers (and other custom methods) within the scope of the XAML <Code> element, it is preferable to define a separate C# file to define the implementation logic, leaving the *.xaml files to contain nothing but UI markup content. Assume the following code-behind file, MainWindow.xaml.cs (by convention, the name of a C# code-behind file takes the form *.xaml.cs):

// MainWindow.xaml.cs using System;

using System.Windows;

using System.Windows.Controls;

namespace SimpleXamlApp

{

public partial class MainWindow : Window

{

public MainWindow()

{

//Remember! This method is defined

//within the generated MainWindow.g.cs file.

InitializeComponent();

}

private void btnExitApp_Clicked(object sender, RoutedEventArgs e)

{

// Get a handle to the current application and shut it down. Application.Current.Shutdown();

}

}

}

Here, we have defined a partial class (to contain the event handling logic) that will be merged with the partial class definition of the same type in the *.g.cs file. Given that InitializeComponent() is defined within the MainWindow.g.cs file, our window’s constructor makes a call in order to load and process the embedded BAML resource.

1030 CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

If desired, we could also build a code-behind file for our Application-derived type. Because most of the action takes place in the MyApp.g.cs file, the code within MyApp.xaml.cs is little more than the following:

// MyApp.xaml.cs using System;

using System.Windows;

using System.Windows.Controls;

namespace SimpleXamlApp

{

public partial class MyApp : Application

{

private void AppExit(object sender, ExitEventArgs e)

{

MessageBox.Show("App has exited");

}

}

}

Before we recompile our files using msbuild.exe, we need to update our *.csproj file to account for the new C# files to include in the compilation process, via the <Compile> elements (shown in bold):

<Project DefaultTargets="Build" xmlns= "http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup>

<RootNamespace>SimpleXamlApp</RootNamespace>

<AssemblyName>SimpleXamlApp</AssemblyName>

<OutputType>winexe</OutputType>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" /> <Reference Include="WindowsBase" /> <Reference Include="PresentationCore" />

<Reference Include="PresentationFramework" /> </ItemGroup>

<ItemGroup>

<ApplicationDefinition Include="MyApp.xaml" />

<Compile Include = "MainWindow.xaml.cs" /> <Compile Include = "MyApp.xaml.cs" />

<Page Include="MainWindow.xaml" /> </ItemGroup>

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />

</Project>

Once we pass our build script into msbuild.exe, we find once again the same executable assembly. However, as far as development is concerned, we now have a clean partition of presentation (XAML) from programming logic (C#). Given that this is the preferred method for WPF development, you’ll be happy to know that WPF applications created using Visual Studio 2008 always make use of the code-behind model just presented.

Source Code The CodeBehindXamlApp project can be found under the Chapter 28 subdirectory.

CHAPTER 28 INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML

1031

The Syntax of XAML

As mentioned earlier in this chapter, the chances of you needing to manually author reams of XAML markup in your WPF applications will be slim to none, as this task will be done on your behalf using dedicated tools (Visual Studio 2008, Microsoft Expression Blend, etc.). Nevertheless, the more you understand about the syntax of a well-formed *.xaml file, the better equipped you will be to tweak and modify autogenerated markup, and the deeper your understanding of WPF itself. This being said, let’s dig into the core syntax of XAML (subsequent chapters will provide additional XAML syntax examples where required).

Experimenting with XAML Using XamlPad

When you are investigating XAML, you will certainly want to author content and quickly see the end result. To facilitate such exploration, the Microsoft Windows SDK ships with a utility named xamlpad.exe.

Note Strangely enough, neither the .NET Framework 3.5 SDK nor Visual Studio 2008 ship with xamlpad.exe. While you could download the Windows SDK to obtain this tool, the final example program in this chapter will have you create a slimmed-down version of xamlpad.exe using C#.

If you have downloaded the Windows SDK to obtain a copy of xamlpad.exe, you can launch this tool via the Start All Programs Microsoft Windows SDK Tools menu option. Figure 28-10 shows the initial launch of xamlpad.exe, with the Visual Tree Explorer option activated (via the related toolbar button).

Figure 28-10. XamlPad provides real-time display of XAML markup.