Part 4: Introduction to XAML
- Posted: Jun 25, 2013 at 5:21 PM
- 148,521 Views
- 67 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
Right click “Save as…”
In this lesson, I want to talk about the XAML syntax we wrote in our first pass at the SoundBoard app. Hopefully you could see how the XAML we wrote impacted what we saw in the Phone preview pane. It's relatively easy to figure out the absolute basics of XAML just by looking at it, but I want to point out some of the features and functions that may not be obvious at first glance.
At a high level, here's our game plan in this lesson:
My aim is by the end of this lesson you'll have enough knowledge that you can look at the XAML we write in the remainder of this series and be able to take a pretty good guess at what it's doing before I even try to explain it.
In the previous lesson, I made a passing remark about XAML and how it looks similar to HTML. That's no accident. XAML is really just XML, the eXtensible Markup Language. I'll explain that relationship in a moment, but at a higher level, XML looks like HTML insomuch that they share a common ancestry. Whereas HTML is specific to structuring a web page document, XML is more generic. By "generic" I mean that you can use it for any purpose you devise and you can define the names of the elements and attributes to suit your needs. In the past, developers have used XML for things like storing application settings, or using it as a means of transferring data between two systems that were never meant to work together. To use XML, you define a schema, which declares the proper names of elements and their attributes. A schema is like a contract. Everyone agrees—both the producer of the XML and the consumer of the XML abide by that contract in order to communicate with each other. So, a schema is an important part of XML. Keep that in mind … we’ll come back to that in a moment.
XAML is a special usage of XML. Obviously, we see that, at least in this case, XAML has something to do with defining a user interface in our Phone's interface. So in that regard, it feels very much like HTML. But there’s a big difference … XAML is actually used to create instances of classes and set the values of the properties. So, for example, in the previous lesson we defined a Button control in XAML:
... that line of code is roughly equivalent to this in C#:
I've added this C# code in the constructor of my MainPage class. I'll talk about the relationship between the MainPage.xaml and MainPage.xaml.cs in just a moment, but we've already seen how we can define behavior by writing procedural C# code in the MainPage.xaml.cs file. Here, I'm merely writing code that will execute as soon as a new instance of the MainPage class is created by writing the code in the constructor of that class.
At this point, I now have two buttons ... one defined declaratively in XAML that has the content of "Hello World" and will quack when we click the button, and a new second button that has the content of "Quack". When we run the application:
we can see only one button. That's because the button we just created procedurally in C#, in the constructor of the MainPage class, is sitting on top of the first button we created in the previous lesson in XAML. Just to prove it, I'll add one more line of C# code that will set a margin on the new Quack button, moving it to the left 210 pixels:
The Margin property of the Button is of type Thickness, a general purpose class that represents 4 dimensions. In this case, we create a new Thickness class and set it's first constructor argument to 210 pixels. When we run the application again:
... we can now see both buttons.
The larger issue is that we have two (almost) identical buttons ... one created declaratively in XAML and the other procedurally in C#.
When I create a new XAML element like so:
I'm basically creating a new instance of the Button class.
When I set attributes on the Button element, I'm basically setting properties on the instance of the Button class.
The important take away is this: XAML is simply a way to create instances of classes and set those objects' properties in a much more simplified, succinct syntax. What took us 10 lines of C# code we were able to accomplish in just one line of XAML (even if I did separate it on to different lines in my editor, it's still MUCH SHORTER than it would have been had I used C# to create my objects.
Furthermore, using XAML I have this immediate feedback in the Phone preview pane. I can see the impact of my changes instantly. In the case of the procedural C# code I wrote, I would have to run the app each time I wanted to see how my tweaks to the code actually worked.
If you have a keen eye, you might notice the difference in the XAML and C# versions when it comes to the HorizontalAlignment attribute / property … If you tried:
myButton.HorizontalAlignment = “Left”;
… you would get a compilation error. The XAML parser will perform a conversion to turn the string value "Left" into the enumeration value System.Windows.HorizontalAlignment.Left through the use of a Type Converter. A Type Converter is a class that can translate from a string value into a strong type—there are several of these built into the Windows 8 API that we’ll use throughout this series. In this example, the HorizontalAlignment property, when it was developed by Microsoft’s developers, was marked with a special attribute in the source code which signals to the XAML parser to run the string value through a type converter method to try and match the literal string "Left" with the enumeration value System.Windows.HorizontalAlignment.Left.
Just for fun, take a look at what happens when you attempt to misspell “Left”:
… you’ll get a compilation error because the Type Converter can’t find an exact match that it can convert into the enumeration value System.Windows.HorizontalAlignment.Left.
So, the first characteristic of XAML is that it is a succinct means of creating instances of classes. In the context of building a Windows 8 application, it is used to create instances of user interface elements, however XAML is not just a user interface technology—it could be used for other purposes in other technologies.
Next, let's talk about all that XAML code at the very top of the MainPage.xaml file ... we ignored it until now.
At the very top of the file, we see the following:
While you're looking this over, remember what I said a moment ago—about schemas being a part of XML. If that's the case, then where does this XAML promise to adhere to a schema?
See lines 3 through 8 ... there are SIX schemas this MainPage.xaml is promising to adhere to. Each is defined with the xmlns attribute. The first xmlns defined in line 3 is the default namespace—in other words, there's no colon and no word after the colon like you see in lines 4 through 8.
The rest of the namespaces in lines 4 through 8 will use name / colon combination. So, just to be clear ... the :x or :phone is the NAMESPACE, that is associated with a SCHEMA (what we've called a contract). Each element and attribute in the rest of this MainPage.xaml MUST ADHERE TO AT LEAST ONE OF THESE SCHEMA's, otherwise the document is said to be invalid. In other words, if there's an element or attribute expressed in this XAML file that is not defined in one of these namespaces, then there's no guarantees that the compiler—the program that will parse through our source code and create an executable that will run on the Phone—the compiler will not be able to understand how to carry out that particular instruction.
So, in this example:
<Grid x:Name="LayoutRoot" Background="Transparent">
We would expect the element Grid and attribute Background to be part of the default schema corresponding with the default namespace defined at the location in line 3.
However, x:Name is part of the schema corresponding with the x: namespace defined at the location in line 4.
I have a bright idea ... let's try to navigate to default namespace to learn more about what makes up a namespace in the first place:
What?! The schema doesn’t actually exist at that URL! That’s because the schema is not published in the sense that you can go to that URL and view it. Instead, a schema is simply a unique name, similar to how we used Namespaces in C# to identify two classes that may have the same name—the schema (and therefore, the namespace in our XAML) keeps class names sorted out, kind of like a last name or surname. This URL, or more properly, we should refer to it as a URI (Uniform Resource IDENTIFIER … rather than LOCATOR) is used as a namespace identifier. The XML namespaces are instructions to the various applications that will parse through the XAML … the Windows Runtime XAML parser will be seeking to turn it into executable code, while the Visual Studio and Blend designers will be seeking to turn it into a design-time experience.
So, the second XML Namespace defines a mapping, x: as belonging to this schema:
Therefore, any elements or attribute names that are preceded by the x: prefix means that they adhere to this second schema.
But, what’s the difference? It’s subtle, but the second schema defines the intrinsic rules for XAML in general. The first schema defines the contract / rules for Windows 8 specific usage of XAML. In other words, the fact that we can work with the Grid, Button, MediaElement and the other Windows Phone 8 XAML elements without using a prefix means that they are defined in the default namespace.
Lines 5 & 6 define namespaces and schemas for the Phone and Shell, which are at a different URI, one that takes is cues from the Microsoft.Phone CLR namespace as defined in assemblies that were installed on our computers after we installed the Windows Phone 8 API. As you can see, the very first line:
indicates that the PhoneApplicationPage class itself is part of this definition. The PhoneApplicationPage derives from Windows.System.Controls.Page ... that just happens to be the same class that is a parent to Windows Presentation Foundation page classes, and Windows Store app page classes. It has a lot of the base functionality shared by all three of these project types. So, the upside is that you're able to leverage what you learn in this series to building WPF desktop apps and Windows Store apps. There will be differences, but there's a lot in common, too!
Lines 7 & 8 define namespaces and schemas that are used to allow Visual Studio's Phone Preview Pane on the left to display properly. These instructions are ignored at runtime, which is what line 9 is doing—when compiling the XAML code, ignore anything prefixed with :d.
Ok, I know there are some questions left unanswered … we could spend a lot of time talking about the specifics, but the main takeaway is that this code at the very top of each XAML file you add to your phone project does have a purpose, it defines the rules that your XAML code must follow. You'll almost never need to modify this code, but if you remove it, you could potentially break your application. So, I would encourage you to not fiddle around with it unless you have a good reason to. There are a few additional attributes in lines 10 through 14 ... we may talk about them later in this series.
In Visual Studio’s Solution Designer, you can see that the XAML files have an arrow which means that we can expand them to reveal a C# file by the same name, the only difference is that it has a .cs file name extension appended to the end. If you look at the .cs version of the file, you’ll see that it defines a MainPage class, and furthermore, it defines it as a PARTIAL class:
The other half of the equation is defined in lines 1 & 2 of the MainPage.xaml:
While it doesn't use the term Partial like it's procedural counterpart, it does indicated the relationship between the two.
Why is this important? This relationship means that the compiler will combine the output of the MainPage.xaml and the MainPage.xaml.cs files into a SINGLE CLASS. This means that they are two parts of a whole. That’s an important concept … that the XAML gets compiled into Intermediate Language just like the C# gets compiled into Intermediate Language and they are both partial implementations of a single class. This allows you to create an instance of a class in one file then use it in the other file, so to speak. This is what allows me to create an instance of the MediaElement class called _audioPlayer in XAML and then call its methods or set its properties in C#. We’ll see more examples of this later in this lesson.
Since XAML is essentially an XML document, we can embed elements inside of other elements. We've already seen an example of this:
<Grid ... >
<MediaElement ... />
<Button ... />
Here PhoneApplicationPage "contains" a Grid and the Grid "contains" a MediaElement and Button. Or, perhaps more correctly in XAML parlance, UserControl's Content property is set to Grid, and Grid's Children collection includes the MediaElement and Button. Depending on the type of control you're working with, the default property can be populated using this embedded style syntax. So, you could do this:
<Button Content="Hello World" ... />
... or this ...
<Button ... >
since the Content property is the default property of the Button class.
In some cases, merely setting attribute values masks the complexity of what's going on behind the scenes. A good example of this is setting Background="Red". We've already seen this procedurally in C# -- to accomplish the same thing:
myButton.Background = newSolidColorBrush(Colors.Red);
... we have to create a new instance of a SolidColorBrush and pass in an enumerated Colors value. This is another great example of a property type converter that we learned about earlier in this lesson. But some attributes are simply too complex to be represented as attributes.
When a properties is not easily represented as a XAML attribute, it's referred to as a "complex property". To demonstrate this, first I'm going to remove the Background="Red" attribute from the Button, remove "Hello World!" as the default property, and add it back with a Content="Hello World!" attribute:
Next, in the Properties pane, I'm going to set the Background property to a linear gradient brush.
I should now see the following in the Phone Preview pane:
... but more importantly, let's look at the XAML that was generated by the Brush editor:
The XAML required to create that background cannot be easily set in a simple literal string like before when we simply used the word "Red". Instead, notice how the Background property is broken out into its own element:
<Button ... >
This is called "property element" syntax and is in the form <Control.Property>.
A good example is the LinearGradientBrush. The term “brush” means that we’re working with an object that represents a color or colors. Think of “brush” like a paint brush … this particular paint brush will create a gradient that is linear—the color will change from top to bottom or left to right. Now, admittedly, you would NEVER want to do what I’m doing in this code example because it goes against the aesthetic of all Windows Phone 8 applications. But, let’s pretend for now that we’re expressing our individuality by using a gradient color as the background color for a Button.
As you can see (below), if we want to define a LinearGradientBrush, we have to supply a lot of information in order to render the brush correctly ... the colors, at what point that color should break into the next color, etc. The LinearGradientBrush has a collection of GradientStop objects which define the colors and their positions in the gradient (i.e., their "Offset").
However, the XAML representing the LinearGradientBrush in the code snippet above is actually SHORTENED automatically by Visual Studio. Here's what it should be:
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Red" Offset="1" />
<GradientStop Color="Black" Offset="0" />
Notice how the <LinearGradientBrush.GradientStops> and <GradientStopCollection> elements are omitted? This is done for conciseness and compactness and is made possible by an intelligent XAML parser. First of all, the GradientStops property is the default property for the LinearGradientBrush. Next, GradientStops is of type GradientStopCollection and implements IList<T>, the T in this case would be of type GradientStop. Given that, it is possible for the XAML parser to deduce that the only thing that could be nested inside the <LinearGradientBrush ... /> is one or more instances of GradientBrush, each being implicitly .Add()'ed to the GradientStopCollection.
So the moral of the story is that XAML allows us to create instances of classes declaratively, and we have a granular fidelity of control to design user interface elements. Even so, the XAML parser is intelligent and doesn’t require us to include redundant code—as long as it has enough information to create the object graph correctly.
To recap, we've learned about the syntax of XAML. Most of XAML is pretty straightforward, but there are a few things that are not quite as obvious:
Next, let's learn more about the Grid for layout, we'll learn about XAML's attached property syntax and how events are wired up in the next lesson.