3. Controlling Class and Member Variable Declarations
Consider the following XAML <Window> definition that makes use of the ClassModifier and FieldModifier keywords as well as x:Name and x:Class (remember that kaxaml.exe will not allow you to make use of any XAML keyword that entails code compilation, such as x:Code, x:FieldModifier, or x:ClassModifier):
<!-- This class will now be declared internal in the *.g.cs file -->
<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" Content = "OK"/>
</Window>
By default, all C#/XAML type
definitions are public, while members default to internal. However,
based on your XAML definition, the resulting autogenerated file contains
an internal class type with a public Button tButton type:
internal partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector
{
public System.Windows.Controls.Button myButton;
...
}
4. XAML Elements, XAML Attributes and Type Converters
Once you have established your root element and any required XML namespaces, your next task is to populate the root with a child element. In a real-world WPF application, the child will be a layout manager (such as a Grid or StackPanel)
which contains, in turn, any number of additional UI elements that
describe the user interface.
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.
To illustrate, enter the following <Button> definition into kaxaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<!-- Configure the look and feel of a Button -->
<Button Height="50" Width="100" Content="OK!"
FontSize="20" Background="Green" Foreground="Yellow"/>
</Grid>
</Page>
Notice that the values
assigned to each property have been captured as a simple text value.
This may seem like a complete mismatch of data types because if you were
to make this Button in C# code, you would not
assign string objects to these properties but would make use of
specific data types. For example, here is the same button authored in
code:
public void MakeAButton()
{
Button myBtn = new Button();
myBtn.Height = 50;
myBtn.Width = 100;
myBtn.FontSize = 20;
myBtn.Content = "OK!";
myBtn.Background = new SolidColorBrush(Colors.Green);
myBtn.Foreground = new SolidColorBrush(Colors.Yellow);
}
As it turns out, WPF ships with a number of type converter
classes, which will be used to transform simple text values into the
correct underlying data type. This process happens transparently (and
automatically).
While this is all well and
good, there will be many times when you need to assign a much more
complex value to a XAML attribute, which cannot be captured as a simple
string. For example, let's say you want to build a custom brush to set
the Background property of the Button. If you are building the brush in code, it is quite straightforward:
public void MakeAButton()
{
...
// A fancy brush for the background.
LinearGradientBrush fancyBruch =
new LinearGradientBrush(Colors.DarkGreen, Colors.LightGreen, 45);
myBtn.Background = fancyBruch;
myBtn.Foreground = new SolidColorBrush(Colors.Yellow);
}
How, however, can you
represent your complex brush as a string? Well, you can't! Thankfully
XAML provides a special syntax that can be used whenever you need to
assign a property value to a complex object, termed property-element
syntax.
5. Understanding XAML Property-Element Syntax
Property-element syntax allows you to assign complex objects to a property. Here is an XAML description for a Button that makes use of a LinearGradientBrush to set its Background property:
<Button Height="50" Width="100" Content="OK!"
FontSize="20" Foreground="Yellow">
<Button.Background>
<LinearGradientBrush>
<GradientStop Color="DarkGreen" Offset="0"/>
<GradientStop Color="LightGreen" Offset="1"/>
</LinearGradientBrush>
</Button.Background>
</Button>
Notice that within the scope of the <Button> and </Button> tags, you have defined a sub-scope named <Button.Background>. Within this scope, you have defined a custom <LinearGradientBrush>.
Generally speaking, any property can be set using property-element syntax, that always breaks down to the following pattern:
<DefiningClass>
<DefiningClass.PropertyOnDefiningClass>
<!-- Value for Property here! -->
</DefiningClass.PropertyOnDefiningClass>
</DefiningClass>
While any property could
be set using this syntax, if you can capture a value as a simple
string, you will save yourself typing time. For example, here would be a
much more verbose way to set the Width of your Button:
<Button Height="50" Content="OK!"
FontSize="20" Foreground="Yellow">
...
<Button.Width>
100
</Button.Width>
</Button>
6. Understanding XAML Attached Properties
In addition to property-element syntax, XAML defines a special syntax used to set a value to an attached property.
Essentially, an attached property allows a child element to set the
value for a property that is actually defined in a parent element. The
general template to follow looks like this:
<ParentElement>
<ChildElement ParentElement.PropertyOnParent = "Value">
</ParentElement>
The most common use of attached property syntax is to position UI elements within one of the WPF layout managers classes (Grid, DockPanel, etc.). Enter the following in kaxaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas Height="200" Width="200" Background="LightBlue">
<Ellipse Canvas.Top="40" Canvas.Left="40" Height="20" Width="20" Fill="DarkBlue"/>
</Canvas>
</Page>
Here, you have defined a Canvas layout manager that contains an Ellipse. Notice that the Ellipse is able to inform its parent (the Canvas) where to position its top/left position using attached property syntax.
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 property of any parent. For example, the following XAML cannot be parsed without error:
<!-- Error! Set Background property on Canvas via attached property? -->
<Canvas Height="200" Width="200">
<Ellipse Canvas.Background="LightBlue"
Canvas.Top="40" Canvas.Left="90"
Height="20" Width="20" Fill="DarkBlue"/>
</Canvas>
In reality, attached properties are a specialized form of a WPF-specific concept termed a dependency property.
Unless a property was implemented in a very specific manner, you cannot
set its value using attached property syntax.
NOTE
Kaxaml, Visual
Studio 2010, and Expression Blend all have IntelliSense, which will show
you valid attached properties that can be set by a given element.
7. Understanding XAML Markup Extensions
As explained, property
values are most often represented using a simple string or via
property-element syntax. There is, however, another way to specify the
value of a XAML attribute, using markup extensions.
Markup extensions allow a XAML parser to obtain the value for a
property from a dedicated, external class. This can be very beneficial,
given that some property values require a number of code statements to
execute to figure out the value.
Markup extensions provide a
way to cleanly extend the grammar of XAML with new functionality. A
markup extension is represented internally as a class that derives from MarkupExtension.
Note that the chances of your ever needing to build a custom markup
extension will be slim to none. However, a subset of XAML keywords (such
as x:Array, x:Null, x:Static, and x:Type) are markup extensions in disguise!
A markup extension is sandwiched between curly brackets, like so:
<Element PropertyToSet = "{MarkUpExtension}"/>
To see a markup extensions in action, author the following into kaxaml:
<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>
<!-- The Static markup extension lets us obtain a value
from a static member of a class -->
<Label Content ="{x:Static CorLib:Environment.OSVersion}"/>
<Label Content ="{x:Static CorLib:Environment.ProcessorCount}"/>
<!-- The Type markup extension is a XAML verion of
the C# typeof operator -->
<Label Content ="{x:Type Button}" />
<Label Content ="{x:Type CorLib:Boolean}" />
<!-- Fill a ListBox with an array of strings! -->
<ListBox Width="200" Height="50">
<ListBox.ItemsSource>
<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>
</ListBox.ItemsSource>
</ListBox>
</StackPanel>
</Page>
First of all, notice that the <Page> definition has a new XML namespace declaration, which allows you to gain access to the System namespace of mscorlib.dll. With this XML namespace established, you first make use of the x:Static markup extension and grab values from OSVersion and ProcessorCount of the System. Environment class.
The x:Type markup
extension allows you to gain access to the metadata description of the
specified item. Here, you are simply assigning the fully qualified names
of the WPF Button and System.Boolean types.
The most interesting part of this markup is the ListBox. Here, you are setting the ItemsSource property to an array of strings declared entirely in markup! Notice how the x:Array markup extension allows you to specify a set of sub-items within its scope:
<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>
Figure 3 shows the mark up of this <Page> in kaxaml.
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 you 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.