Monday, June 25, 2007

A Mono Developers Guide to writing XAML by hand.

Most UI creating markup languages do not require the developer to know very much about them. A developer rarely hand writes their Glade by hand, but the dynamic nature and lack of free tools for creating XAML, will force a lot of developers to hand write XAML code.

XAML is an XML based language, but unlike many other XML based languages, which are strict and cumbersome, some effort has been made to make XAML easy to write by hand. Below, I'll try to cover everything you need to know, to write XAML code for Moonlight projects.

What is it for?


In the context of this article XAML is used to create Silver/Moonlight applications. XAML is also used to create WPF applications, but I can't really comment on that technology (until we implement it in 28 days). A typical Moonlight XAML file will contain a root Canvas element, and a number of elements used to define the applications interface.

The following example draws a line from 10, 10 to 100, 100.


<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Line stroke="Red" x:Name="MyLine" X1="10" Y1="10" X2="100" Y2="100" />
</Canvas>


In the above example everything looks pretty self explanatory except for thexmlns stuff. If you aren't familiar with xml, the xmlns="..." chunks are declaring XML namespaces. You will need to use at least the first xmlns declaration in all of your XAML files. The second namespace declaration is used so that we have access to the XAML x: namespace. This namespace is used for some special XAML features that we will talk about later.

Case Sensitivity


XAML is a XML based language, and by that nature, case-sensitive. Using <canvas> or <line> in a XAML file will raise an error in the parser. The gotcha to XAML's case sensitivity is that, properties and attribute values are often (almost always) NOT case sensitive.  So Stroke="Red" will yield the same results as Stroke="RED" or Stroke="red", stroke="Red" will not work though. This is because property values are parsed by the elements type converters, and not by the parser.  The easy way to remember this is anything outside of "" is case sensitive, and anything inside of "" is probably not (but could be).

Well Formedness


The XAML parser doesn't do you any favors.  You XAML code needs to be well formed XML.  You can't give the parser something like this:


<Canvas><Line> </Canvas>
(Note that the line is unclosed)


and expect the parser to figure things out for you.  In the words of Sam Ruby, Silverlight is draconian. Not only is Silver/Moonlight draconian about errors, but it often will swallow errors and just not display your elements. If you are using Mono's Moonlight, you can build libmoonlight with DEBUG_XAML defined and get lots of extra information about your files, which can really help with debugging your application.

Setting Properties


There are a few ways to set properties in XAML. We've already seen a property set with attribute syntax above, but attribute syntax can only be used when you are setting a property to something that can be defined with a simple string, like "Red", "10", or "MyLine". If we want to set a property to something more complex, like a Gradient brush, we are going to need to use Property Element Syntax. The following example sets the line's stroke brush using Property Element Syntax:


<Line x:Name="MyLine" X1="10" Y1="10"X2="100" Y2="100">
<Line.Stroke>
<SolidColorBrush Color="Red" />
</Line.Stroke>
</Line>


In the above example we declared out Line element, but we didn't close the element right away, we left it open, and then opened a property element using <line.stroke>.  Properties are declared with <typename>.<propertyname>.  Since the Line.Stroke property is a Brush, all we have to do is stick a brush inside of the property declaration, and our property will be set to that Brush.

Line is defined as:


class Line : Shape


and Lines inherit the Stroke property from Shapes, so something like this:


<Line x:Name="MyLine" X1="10" Y1="10" X2="100" Y2="100">
<Shape.Stroke>
<SolidColorBrush Color="Red" />
</Shape.Stroke>
</Line>


would also work. Rectangles also have a stroke property, so would this work?


<Line x:Name="MyLine" X1="10" Y1="10" X2="100" Y2="100">
<Rectangle.Stroke>
<SolidColorBrush Color="Red">
</Rectangle.Stroke>
</Line>


Nope. The property type name needs to be in the inheritance tree of the element we are setting the property on, otherwise the parser will fail.

You might be wondering, how does the parser turn "Red" in attribute syntax into <SolidColorBrush Color="Red"> in property element syntax.  Well the Stroke and Fill properties are somewhat unique.  Their type converters will take a Color in string form, and turn them into a SolidColorBrush with the color represented by that string.  You can define colors using names, like Yellow, Orange, Blue or you can use RGB.  There are too many ways of defining colors in XAML for me to describe here.

Collections


Not all properties take a single value.  XAML also supports Collections.  To set a collection in XAML, you simply open the collection element, and add a bunch of properly typed elements to it.

In the following example we will create a rectangle, and fill it using a LinearGradientBrush.  The LinearGradientBrush uses a Collection for setting it's GradientStops:


<Canvas xmlns="http://schemas.microsoft.com/client/2007">
<Rectangle Width="200" Height="200">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<LinearGradienBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Green" Offset="0.50" />
<GradientStop Color="Blue" Offset="1.00" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>


The GradientStopCollection, inherits from Collection, and can therefore have collection items added to it.  There is type safety here, so you can only add GradientStops to a GradientStopCollection or you will get a nasty error.

Content Properties


You were probably reading the above example and thinking to yourself, "I thought he said XAML wasn't cumbersome?".  The above example can be simplified using a XAML feature known as Content Properties. Content Properties allow elements to have an implicit property that can be set without defining the <type.property>.  Using Content Properties, we can simplify the above example to:


<Canvas xmlns="http://schemas.microsoft.com/client/2007">
<Rectangle Width="200" Height="200">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Green" Offset="0.50" />
<GradientStop Color="Blue" Offset="1.00" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>


Above we simplified the example and removed four lines of code by using a single content property. LinearGradientBrush's content property is defined as GradientStops, which is a GradientStopCollection. This means we can just add GradientStops right to the LinearGradientBrush. The following example would also be legal:


<Canvas xmlns="http://schemas.microsoft.com/client/2007">
<Rectangle Width="200" Height="200">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStopCollection>
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Green" Offset="0.50" />
<GradientStop Color="Blue" Offset="1.00" />
</GradientStopCollection>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>


Note that in this example, we used the LinearGradientBrush's GradientStops content property, but we still defined the GradientStopCollection.

Content Properties aren't just for collections. The Run class, takes a text content property, allowing you to define text blocks like this:


<Run Font="Arial">Hi there, I am your text</Run>


However, in Silver/Moonlight, most content properties are collections.

x:Name


x:Name allows you to name the instance of an element. In the following example, we will create three Lines, and name each one individually:


<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Line stroke="Red" x:Name="MyLineOne" X1="10" Y1="10" X2="100" Y2="100" />
<Line stroke="Red" x:Name="MyLineTwo" X1="10" Y1="10" X2="100" Y2="100" />
<Line stroke="Red" x:Name="MyLineThree" X1="10" Y1="10" X2="100" Y2="100" />
</Canvas>


Naming elements allows us to easily find these elements in our C# code using theFindNamefunction. If you are using Visual Studio, it will also allow you to magically access your element like this:


MyLineOne.X1 = 25;


VisualStudio creates some partial classes that insert type declarations and FindName's for you.

Naming elements is also necessary if you want to animate elements.

Accessing Properties


You can access properties of XAML elements using PropertyPath syntax. PropertyPath syntax is resolved at runtime, and in Silver/Moonlight is only used for animations. Here is a simple example that animates a Rectangle's Brush's color property:


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

<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
</Storyboard>
<ColorAnimation Storyboard.TargetName="MyRectsBrush"
Storyboard.TargetProperty="Color"
From="Red" To="Blue"
Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>

<Rectangle x:Name="MyRect" Width="100" Height="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MyRectsBrush" Color="Red" />
</Rectangle.Fill>
</Rectangle>
</Canvas>


Ignore all the Trigger, Begin and Storyboard code and focus on the ColorAnimation line. Note that we are targetting the rectangle's brush property with Storyboard.TargetName="MyRectsBrush" and we are targetting the Color property of that brush with Storyboard.TargetProperty="Color". This animation will change the color of our Rectangles Fill from Red to Blue, over a five second period.

In the above example we were forced to use Property Element Syntax for setting the Brush's Color because we needed to name the Brush, so it could be animated. The following example uses Property Paths, to access the Brush, without naming it:


<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="MyRect"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
From="Red" To="Blue" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>

<Rectangle x:Name="MyRect" Fill=”Red” Width="100" Height="100" />
</Canvas>


We don't need to be that specific in our property path code either, this would have worked just as well:


Storyboard.TargetName="(Fill).(Color)"


In property path syntax, everything within ( ) brackets resolve to a property, if we add a . after a property, we are accessing the object value of that property. We can also access items in collections using [ ] brackets. In the following example, we will animate the second GradientStop of a rectangle:


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

<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="MyRect"
Storyboard.TargetProperty="(Rectangle.Fill).(LinearGradientBrush.GradientStops)[1].(GradientStop.Color)"
From="Green" To="Yellow" Duration="0:0:5" >
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>

<Rectangle x:Name="MyRect" Width="200" Height="200">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStopCollection>
<GradientStop Color="Red" Offset="0.0" />
<GradientStop Color="Green" Offset="0.50" />
<GradientStop Color="Blue" Offset="1.00" />
</GradientStopCollection>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>


x:Class


XAML is a powerful language, but it is only meant to be used for defining an application's user interface. One method of adding application logic to a Silver/Moonlight application, is the x:Class attribute.

The x:Class attribute allows you to create a custom class, that extends Canvas. The following example creates a custom class that extends Canvas and prints the current user's name.


<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Mono.UserCanvas;assembly=UserCanvas.dll>
</Canvas>


Now in C#:


namespace Mono.Fruit {
public class UserCanvas : Canvas {

public UserCanvas ()
{
TextBlock user = new TextBlock ();
user.Text = Environment.UserName;
Children.Add (user);
}
}
}


Using x:Class you are also able to easily handle events. You can handle mouse clicks by adding the following to your C# class:


protected override void OnMouseLeftButtonDown (MouseButtonEventArgs e)
{
}


The one gotcha to x:Class is that you can only set the x:Class attribute on the top element of your XAML file.

Custom Tags


Another method of adding application logic to a XAML file is to use custom tags. Custom tags allow a little more flexibility, because you can set custom properties on them from the XAML code. The following example will create a custom class and set some custom properties on it:


<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:fruit="clr-namespace:Mono.Fruit;assembly=Fruit.dll">
<fruit:Watermelon Height="100" Width="100" fruit:Weight="200" fruit:Seedless="True" />
</Canvas>


Our C# code will look something like this:


namespace Mono {
public class Watermelon : Canvas {
private int weight;
private bool seedless;

public int Weight {
get { return weight; }
set { weight = value; }
}

public bool Seedless {
get { return seedless; }
set { seedless = true; }
}
}
}


In the above example we created a watermelon and set the Height and Width properties on it, we got both of those properties for free because our Watermelon class extends Canvas. We also set some custom properties, Weight and Seedless. Setting these properties doesn't require any special markup or attributes on the properties in the C# code, but the properties must have available type converters from String. So the simple types like int, bool, double, and obviously string will work, but a lot of the classes in agclr that don't include a type converter, like Color or KeySpline will not work. If you want to be able to set an attribute value to a type that you created, you can define a type converter for that class.

That's it! You should now understand all the important concepts in XAML.

10 comments:

Cedric Vivier said...

Congrats for your guide!

There may be a little typo though on the Watermelon declaration : XAML's clr-namespace=Mono.Fruit vs C#'s namespace Mono {

jacksonh said...

Cedric, thank you very much. I fixed the example.

Anonymous said...

Great guide.

Just a small niggle - XAML is not just for designing GUI's; it's simply a means of defining an object map in XML. As I understand it, any CLR object can be instantiated and initialised from XAML.

Andrew

jacksonh said...

Andrew, You can not instantiate any CLR object from XAML, especially not in the Silverlight subset of XAML. In Silverlight, you can only instantiate some of the classes in agclr, and custom types. So in the case of Silverlight, XAML is really just for user interfaces.

Don Burnett said...

Just a clarification here, on the XAML used for other things category..

Microsoft introduced a XAML version with the Acropolis CTP that lets you create rich WPF client apps designed to work within the framework of the Microsoft Enterprise Library (and Microsoft Patterns and Practice).

You can use this new XAML, with data binding to bind user interface (UI) elements directly to part connection points. You can also use XAML to configure aspects of a part, form, or application, such as the following:

Connection points.

Child parts.

Service dependencies.

Services made available to child parts.

Navigation manager.


The benefit of the new "Acropolis" Designer's XAML configuration capability is that it makes it easy for you to define complex application structures without writing code. XAML provides a consistent syntax that makes it human readable and editable, but it is primarily useful with tools such as the "Acropolis" designer.

So XAML is used outside of the context that you mentioned, that allows "client application parts" to communicate with each other and work more effectively.

These things I am referring to as parts are things like you'd see in a multi-pane application like the outlook mail client, where each pane can have independent operation..

Check out the following pages:

http://blog.donburnett.com/2007/06/enterprise-wpf-applications-come-of.html

http://msdn2.microsoft.com/en-us/library/aa139638.aspx

http://windowsclient.net/Acropolis/

http://blogs.msdn.com/acropolis/archive/2007/07/04/acropolis-july-ctp-available-now-kathy-kam.aspx

You will see things like this but with the appropriate "< />" sets...


Windows:PartPane Name="RootPart
Windows:PartPane.Transition WindowsTransitions:RotateTransition
Windows:PartPane.Transition
Windows:PartPane

Yep this is XAML and the use is quite cool..

David F said...

Hello. I have built several applications in Silverlight using javascript and come from a flash background. I work on a mac and was hoping to find a simple "Hello World" example of building a silverlight app using mono and c#.

Do you know if such a simple example exists? I think that and the existing documentation would set me down the right path.

Thanks for any help you could provide.

jacksonh said...

Hey David, checkout the examples/ directory in the moon source. Here is a link to the source for the standalone example.

http://anonsvn.mono-project.com/viewcvs/trunk/moon/examples/standalone-sample/

David F said...

Jackson thanks SO much!

David F said...

One more question.

From what I can tell mono and moonlight are not currently compiling a xap file for deployment in the browser from what I can gather.

Do you know if this is still the case? If it is not do you know of any resources out there? I cannot find a thing.

Sam said...

It is an essential tip that XAML is also used to create WPF applications, but I can't really comment on that technology.
Writing assistance

Blog Archive

About Me