3. Designing the GUI of our Window
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, you will build an application that mimics the basic functionality of the kaxaml.
While your application will certainly not be as feature-rich as kaxaml.exe,
it will provide the ability to enter XAML markup, view the results, and
save the XAML to an external file. To begin, update the initial XAML
definition of your <Window> as:
<Window x:Class="MyXamlPad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="My Custom XAML Editor"
Height="338" Width="1041"
Loaded="Window_Loaded" Closed="Window_Closed"
WindowStartupLocation="CenterScreen">
<!-- You will use a DockPanel, not a Grid -->
<DockPanel LastChildFill="True" >
<!-- 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"/>
</DockPanel>
</Window>
First of all, notice that you 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 fMainWindow.xaml.cs file:
using System.IO;
using System.Windows.Markup;
4. Implementing the Loaded Event
The Loaded event of your 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 you are using a <StackPanel> rather than a <Grid>).
NOTE
The string you are
building to represent the key XML namespaces is a bit cumbersome to
type, given the escape characters required for the embedded quotations,
so 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\"\n"
+"Height =\"400\" Width =\"500\" WindowStartupLocation=\"CenterScreen\">\n"
+"<StackPanel>\n"
+"</StackPanel>\n"
+"</Window>";
}
}
Using this approach,
your 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 7 within the TextBox type.
5. Implementing the Button's Click Event
When you click the Button, 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:
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);
// Show window as a dialog and clean up.
myWindow.ShowDialog();
myWindow.Close();
myWindow = null;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Note that you are wrapping much of our logic within a try/catch block. In this way, if the YourXaml.xaml
file contains ill-formed markup, you can see the error of your ways
within the resulting message box. For example, run your program, and
purposely misspell <StackPanel> by adding an extra letter P in the opening element or whatnot. If you click the button, you will see an error similar to Figure 8:
6. Implementing the Closed Event
Finally, the Closed event of your 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);
}
7. Testing the Application
Now fire up your program and enter some XAML into your text area. Do be aware that (like kaxaml.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 <Window> 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 the message box—watch your
typing!). Figure 9 shows possible output.
Great!
I am sure you can think of many possible enhancements to this
application, but to do so you need to be aware of how to work with WPF
controls and the panels that contain them.