
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1042 CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML
<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:Array Type = CorLib:String}"/> </StackPanel>
</Page>
If you view the rendered markup using xamlpad.exe, you will see the value System.String[] print out in the view pane. Using the expected curly bracket syntax, we have no way to populate the array with data. To do so, we must create our array using subelements that match the specified type of the array. Consider the following partial XAML definition:
<x:Array Type="CorLib:String"> <CorLib:String>Sun Kil Moon</CorLib:String>
<CorLib:String>Red House Painters</CorLib:String> <CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
Here, we have created an array of strings. Within the scope of the <x:Array> type, we add in three textual values and close the definition. While this is valid XAML markup, the next question is, where we can place our array declaration? If we were to place it directly within the scope of a <Page> element, we have just set the Content property of the <Page> implicitly and we would (once again) see System.String[] display in the view port of xamlpad.exe (which is not quite what we are aiming for).
<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">
<!-- Humm, we just set the Content property here! -->
<x:Array Type="CorLib:String"> <CorLib:String>Sun Kil Moon</CorLib:String>
<CorLib:String>Red House Painters</CorLib:String> <CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
</Page>
What we really would like to do is give this array a name and then reference it elsewhere in our markup (e.g., to fill a ListBox). We can do this very thing, if we define our array within a resource element. Now, let me be clear that WPF “resources” do not always map to what we may typically think (string tables, icons, bitmaps, etc.). While they certainly could, WPF resources can be used to represent any custom blob of markup, such as our array of strings (more information on WPF resources can be found in Chapter 30).
Consider the final <Page> definition that adds a string array resource named "GoodMusic" to a <StackPanel> via the Key markup extension. Once we have done so, we then set the ItemsSource property of the ListBox type to our array using the StaticResource markup extension (notice we are referencing the same key at this time):
<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>
<StackPanel.Resources>

CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML |
1043 |
<x:Array Type="CorLib:String" x:Key = "GoodMusic"> <CorLib:String>Sun Kil Moon</CorLib:String> <CorLib:String>Red House Painters</CorLib:String> <CorLib:String>Besnard Lakes</CorLib:String>
</x:Array>
</StackPanel.Resources>
<Label Content ="Really good music"/>
<ListBox Width = "200" ItemsSource ="{StaticResource GoodMusic}"/>
</StackPanel>
</Page>
As you can see, we are nesting within the scope of the <StackPanel> a nested <StackPanel. Resources> element as the home for our array of strings. The StaticResource markup extensions represent any resource that is not expected to change after the initial binding (hence the notion of “static”). If you are working with a resource that may change after the first bind (such as a given system color), you can use the alternative markup extension, DynamicResource. In any case, Figure 28-15 shows how xamlpad.exe looks now.
Figure 28-15. Markup extensions, static resources, and simple data binding
■Note The reason that the Key and StaticResource markup extensions have not been qualified with an x prefix (unlike the other markup extensions examined here) is because they are defined within the root http://schemas.microsoft.com/winfx/2006/xaml/presentation XML namespace (as they are WPF-centric).
So! At this point you have seen numerous examples that showcase each of the core aspects of XAML syntax. As you might agree, XAML is very interesting in that it allows us to describe a tree of
.NET objects in a declarative manner. While this is extremely helpful when configuring graphical user interfaces, do remember that XAML can describe any type from any assembly provided it is a nonabstract type containing a default constructor.


CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML |
1045 |
When you wish to build a WPF desktop application, you’ll want to select the WPF Application project workspace type. Beyond setting references to each of the WPF assemblies (PresentationCore.dll, PresentationFoundation.dll, and WindowsBase.dll), you will also be provided with initial Window- and Application-derived types, making use of code files and an XAML definition (see Figure 28-17).
Figure 28-17. The initial files of a WPF Application project type
Changing the Name of the Initial Window
For a production-level project, you will most certainly wish to rename your initial Window-derived type (and the file that defines it) from the default name of Window1 to a more fitting description. However, given all of the moving parts required by a WPF application, doing so is a bit more complex than meets the eye. Here is a walk-through of the process.
First, if you right-click the name of your initial Window1.xaml file and select the Rename menu option, you will be pleased to find that the related Windows1.xaml.cs is also renamed according to your selection (e.g., MainWindow.xaml). However, the name of the class type within the *.xaml.cs file will still be named Window1. If you right-click the class name within the *.xaml.cs file and select the Refactor Rename option, you will be able to supply a fitting name (MainWindow). At this point, if you attempt to run your program, you will generate a runtime exception!
The first reason for this is that the Class attribute of the opening <Window> element is still referring to the original Window1 class name, which must be updated to match your new class name:
<Window x:
Class="MyWPFApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300">
<Grid>
</Grid>
</Window>
In addition, the StartupUri property in the <Application> declaration must also be updated to specify the name of the renamed XAML file containing the initial Window type (MainWindow.xaml):



1048 CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML
If you manually enter an event name (encased in quotation marks, as required by XAML), you can specify any method name you wish. If you would rather simply have the IDE generate a default name (which takes the form NameOfControl_NameOfEvent), you can double-click the <New Event Handler> pop-up menu item. In either case, the IDE responds by adding the correct event handler in your code file:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
■Note Recall that if you wish the IDE to define a member variable of a control type, you will need to assign a value to the Name property. If you handle events for unnamed controls, the event handler name is simply
TypeOfControl_NameOfEvent[_Number] (e.g., Button_Click, Button_Click_1, Button_Click_2, etc.).
Now that you have seen the basic tools used within Visual Studio 2008 to manipulate WPF applications, let’s leverage this IDE to build an example program that illustrates the process of parsing XAML at runtime.
Processing XAML at Runtime: SimpleXamlPad.exe
The WPF API supports the ability to load, parse, and save XAML descriptions programmatically. Doing so can be quite useful in a variety of situations. For example, assume you have five different XAML files that describe the look and feel of a Window type. As long as the names of each control (and any necessary event handlers) are identical within each file, it would be possible to dynamically apply “skins” to the window (perhaps based on a startup argument passed into the application).
Interacting with XAML at runtime revolves around the XamlReader and XamlWriter types, both of which are defined within the System.Windows.Markup namespace. To illustrate how to programmatically hydrate a Window object from an external *.xaml file, we will create a WPF Application project (named SimpleXamlPad) that mimics the basic functionality of the xamlpad.exe application examined earlier in this chapter.
While our application will certainly not be as feature-rich as xamlpad.exe, it will provide the ability to enter XAML definitions, view the results, and save the XAML to an external file. Once you have created the SimpleXamlPad project using Visual Studio 2008, rename your initial window to MainWindow (using the process described previously) and update the initial XAML definition as so:
■Note The next chapter will dive into the details of working with controls and panels, so don’t fret over the details of the control declarations.
<Window x:Class="SimpleXamlPad.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Simple XAMl Viewer" Height="338" Width="1041"
Loaded="Window_Loaded" Closed="Window_Closed"
WindowStartupLocation="CenterScreen"> <DockPanel LastChildFill="True" >

CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML |
1049 |
<!-- This button will launch a window with defined XAML -->
<Button DockPanel.Dock="Top" Name = "btnViewXaml" Width="100" Height="40" Content ="View Xaml" Click="btnViewXaml_Click" />
<!-- This will be the area to type within -->
<TextBox AcceptsReturn ="True" Name ="txtXamlData"
FontSize ="14" Background="Black" Foreground="Yellow" BorderBrush ="Blue" VerticalScrollBarVisibility="Auto" AcceptsTab="True">
</TextBox>
</DockPanel>
</Window>
First of all, notice that we have replaced the initial <Grid> with a <DockPanel> type that contains a Button (named btnViewXaml) and a TextBox (named txtXamlData), and that the Click event of the Button type has been handled. Also notice that the Loaded and Closed events of the Window itself have been handled within the opening <Window> element. If you have used the designer to handle your events, you should find the following code in your MainWindow.xaml.cs file:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnViewXaml_Click(object sender, RoutedEventArgs e)
{
}
private void Window_Closed(object sender, EventArgs e)
{
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
Before continuing, be sure to import the following namespaces into your MainWindow.xaml.cs
file:
using System.IO;
using System.Windows.Markup;
Implementing the Loaded Event
The Loaded event of our main window is in charge of determining if there is currently a file named YourXaml.xaml in the folder containing the application. If this file does exist, you will read in the data and place it into the TextBox on the main window. If not, you will fill the TextBox with an initial default XAML description of an empty window (this description is the exact same markup as an initial window definition, except that we are using a <StackPanel>, rather than a <Grid>, to set the Window’s Content property [implicitly]).

1050 CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML
■Note The string we are building to represent the XML namespaces is a bit nasty to type, given the escape characters required for the embedded quotations (type carefully).
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//When the main window of the app loads,
//place some basic XAML text into the text block.
if (File.Exists(System.Environment.CurrentDirectory + "\\YourXaml.xaml"))
{
txtXamlData.Text = File.ReadAllText("YourXaml.xaml");
}
else
{
txtXamlData.Text =
"<Window xmlns=\"http://schemas.microsoft.com" +"/winfx/2006/xaml/presentation\"\n" +"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\""
+" Height =\"400\" Width =\"500\" WindowStartupLocation=\"CenterScreen\">\n" +"<StackPanel>\n"
+"</StackPanel>\n"
+"</Window>";
}
}
Using this approach, the SimpleXamlPad.exe application will be able to load the XAML entered in a previous session, or supply a default block of markup if necessary. At this point, you should be able to run your program and find the display shown in Figure 28-21 within the TextBox type.
Figure 28-21. The first run of SimpleXamlPad.exe
Implementing the Button’s Click Event
When you click the Button type, you will first save the current data in the TextBox into the YourXaml. xaml file. At this point, you will read in the persisted data via File.Open() to obtain a Stream-derived type. This is necessary, as the XamlReader.Load() method requires a Stream-derived type (rather than a simple System.String) to represent the XAML to be parsed.
Once you have loaded the XAML description of the <Window> you wish to construct, create an instance of System.Windows.Window based on the in-memory XAML, and display the Window as a modal dialog:

CHAPTER 28 ■ INTRODUCING WINDOWS PRESENTATION FOUNDATION AND XAML |
1051 |
private void btnViewXaml_Click(object sender, RoutedEventArgs e)
{
//Write out the data in the text block to a local *.xaml file.
File.WriteAllText("YourXaml.xaml", txtXamlData.Text);
//This is the window that will be dynamically XAML-ed.
Window myWindow = null;
// Open local *.xaml file. try
{
using (Stream sr = File.Open("YourXaml.xaml", FileMode.Open))
{
// Connect the XAML to the Window object. myWindow = (Window)XamlReader.Load(sr); myWindow.ShowDialog();
}
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
}
Note that we are wrapping much of our logic within a try/catch block. In this way, if the YourXaml.xaml file contains ill-formed markup, we can see the error of our ways within the resulting message box.
Implementing the Closed Event
Finally, the Closed event of our Window type will ensure that the latest and greatest data in the TextBox is persisted to the YourXaml.xaml file:
private void Window_Closed(object sender, EventArgs e)
{
// Write out the data in the text block to a local *.xaml file.
File.WriteAllText("YourXaml.xaml", txtXamlData.Text);
}
Testing the Application
Now fire up your program and enter some XAML into your text area. Do be aware that (like xamlpad.exe) this program does not allow you to specify any code generation–centric XAML attributes (such as Class or any event handlers). As a test, enter the following XAML within your
<StackPanel> scope:
<StackPanel>
<Rectangle Fill = "Green" Height = "40" Width = "200" /> <Button Content = "OK!" Height = "40" Width = "100" /> <Label Content ="{x:Type Label}" />
</StackPanel>
Once you click the button, you will see a window appear that renders your XAML definitions (or possibly you’ll see a parsing error in message box—watch your typing!). Figure 28-22 shows possible output.