After having read part 2 of this tutorial you know how to create a very simple Silverlight application in XAML, displaying some text in the browser. Let's see now how XAML actually works together with C#.
After having read this part you will:
One thing you will immediately notice during your Silverlight adventure is that you can very often do the same thing in many different ways and using different tools. This is great news because everyone have their habits and preferred programming methods, and it is important the tools allow everyone do their job the way they like.
Remember our "Hello World" application from the the previous part? You didn't write a single line of C# code, everything was written in XAML. Open the Page.xaml file. The code looks like this:
<UserControl x:Class="HelloWorld.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="Yellow"> <TextBlock Text="Hello World!" VerticalAlignment="Center" HorizontalAlignment="Center" /> </Grid> </UserControl>
Remove the TextBlock from XAML now:
<UserControl x:Class="HelloWorld.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="Yellow"> </Grid> </UserControl>
You'll notice that the "Hello World!" text is now gone in the designer preview in Visual Studio. And indeed, it is gone when you run the application. Switch to the Page.xaml.cs file now, and put the following code in the Page constructor:
public Page() { InitializeComponent(); TextBlock text = new TextBlock() { Text = "Hello World!", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; LayoutRoot.Children.Add(text); }
You just replaced some XAML with some equivalent C# code. You still cannot see the "Hello World!" text in the preview of the Page.xaml file, but when you hit F5, you can see the text is displayed as it was before. That's because the designer preview only parses XAML code and displays its preview. It doesn't execute the entire constructor of your control. But how is XAML actually parsed? Let's have a closer look at this.
Whenever you're dealing with a class that has both XAML and C# code, there's the InitializeComponent method that should be called in the very first line of the class's constructor. And indeed, it is there for you if you create a new class with the Visual Studio wizard. It is automatically generated for you by Visual Studio whenever you modify the XAML code. If you want to see where exactly this method is defined, turn on the Show All Files option in your project. In the obj/Debug subdirectory of the project you can find these two files: App.g.cs and Page.g.cs.
These are auto generated files, one for each class that contains XAML. If you open Page.g.cs, you will see it contains partial definition of the Page class. The Page class contains the definition of the InitializeComponent method. It also has a field for every element that has the x:Name attribute in the Page.xaml file. For example, it contains an internal field called LayoutRoot that is of type Grid, as defined in XAML:
What InitializeComponent does is: it takes all controls defined in XAML, creates instances of them and sets all the properties you set in XAML. It does all the same you would do if you wrote the C# code manually. In our example when an instance of the Page class is created, InitializeComponent sets Page's Width and Height properties. It also initializes the LayoutRoot field with a new instance of the Grid class, and sets its background to yellow. And because the Grid control is inside of the Page control in XAML, the Grid control is set as the Content property of the Page control.
Now try commenting out the InitializeComponent method call in Page's constructor (in the Page.xaml.cs file). When you run the application it crashes with a NullReferenceException at this line:
LayoutRoot.Children.Add(text);
If you debug the code you can see it crashes because LayoutRoot is null. LayoutRoot has never been initialized because InitializeComponent has never been called.
If you want to get rid of all the XAML code and the InitializeComponent method call in your Page control, you can translate all your XAML to C#. You can do it in the Page.xaml.cs file like this:
public class Page : UserControl { internal Grid LayoutRoot; public Page() { Width = 400; Height = 300; LayoutRoot = new Grid() { Background = new SolidColorBrush(Colors.Yellow) }; Content = LayoutRoot; TextBlock text = new TextBlock() { Text = "Hello World!", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; LayoutRoot.Children.Add(text); } }
Now everything is in C# and we don't use XAML at all (the content from the Page.xaml file is never instantiated). Notice now the entire class is defined just in one place and is not marked as partial anymore. When you used XAML, the class had to be defined as partial, because the other part of the class was defined in the auto generated Page.g.cs file.
If you want the above code to work in your project, you have to also comment out the partial Page class definition in the Page.g.cs file. Otherwise Visual Studio will complain the class is defined twice. Of course, if you modify the Page.xaml file, the Page.g.cs file will get regenerated and your code will stop working again. If you want to get rid of XAML permanently, you should remove the Page.xaml file from the project while keeping the Page.xaml.cs file. If you try to do it in Visual Studio, you'll notice these two files are "grouped" together and you cannot just remove the *.xaml file. To "ungroup" them, you have to open the project file HelloWorld.csproj in a text editor, find this part: <Compile Include="Page.xaml.cs"> <DependentUpon>Page.xaml</DependentUpon> </Compile> and delete the <DependentUpon> element: <Compile Include="Page.xaml.cs"> </Compile> Now you should be able to reload the project in Visual Studio and delete the Page.xaml file while keeping the Page.xaml.cs file.
If you want the above code to work in your project, you have to also comment out the partial Page class definition in the Page.g.cs file. Otherwise Visual Studio will complain the class is defined twice. Of course, if you modify the Page.xaml file, the Page.g.cs file will get regenerated and your code will stop working again. If you want to get rid of XAML permanently, you should remove the Page.xaml file from the project while keeping the Page.xaml.cs file. If you try to do it in Visual Studio, you'll notice these two files are "grouped" together and you cannot just remove the *.xaml file. To "ungroup" them, you have to open the project file HelloWorld.csproj in a text editor, find this part:
<Compile Include="Page.xaml.cs"> <DependentUpon>Page.xaml</DependentUpon> </Compile>
and delete the <DependentUpon> element:
<Compile Include="Page.xaml.cs"> </Compile>
Now you should be able to reload the project in Visual Studio and delete the Page.xaml file while keeping the Page.xaml.cs file.
As you probably noticed, some controls have Content property (in our example Page) and can only have one element set as their child. Other controls have Children property (in our example Grid) and can have entire collection of child controls. When you nest controls inside each other in XAML, the XAML parser acts smart and either sets the inner control as the Content of the outer control, or adds the inner control to the Children collection of the outer control. It all depends on which property is available in the outer control.
Another thing you probably noticed is that the Background property of Grid is of type Brush but in XAML it is initialized simply by color name. This is a shortcut.
This XAML code:
<Grid x:Name="LayoutRoot" Background="Yellow"> </Grid>
is equivalent to this one:
<Grid x:Name="LayoutRoot"> <Grid.Background> <SolidColorBrush Color="Yellow" /> </Grid.Background> </Grid>
which in turn translates to this C# code:
LayoutRoot = new Grid(); LayoutRoot.Background = new SolidColorBrush(Colors.Yellow);
There are more shortcuts like this that make XAML code more compact than C# code :)
At this point you are probably confused and don't know when you should use C# and when XAML. Remember, everything you write in XAML, you can write in C# too. The code above is an example. You removed the TextBlock control from XAML and instantiated it in C# instead, giving it the same properties as previously in XAML. The final result is exactly the same. There are however some differences:
As you see, in some more advanced cases you need to write C# code, because you cannot do everything with XAML. But sometimes you have choice and it's up to you how you write your code: in XAML or C#. I think it is important you learn both. Of course, you can act like an old school programmer and stick with the "real" C# code, ignoring XAML completely, and you can write entire applications this way. However XAML usually allows you to code faster, making you more productive. If that doesn't convince you, your UI designer should convince you. Whoever they are, they won't be able to read your C# code. But they can open your XAML code in Expression Blend, and make your application's UI look cool with a few clicks. I will explain how to do that later on.
I mentioned the mysterious XAP file before so let's have one last look at what happens when you rebuild your project. You have a bunch of C# files (both written by you and automatically generated for you by Visual Studio), and some XAML files. They are all compiled into a DLL file and the AppManifest.xaml file is generated. You can find them in the Bin/Debug subdirectory of your project. These files are then gathered together and compressed to one ZIP file.. that has the XAP extension. Yes, a ZIP file. If you take the HelloWorld.xap file and rename it to HelloWorld.zip, you can open it by double clicking and see its contents. The TestPage.html file is also generated. In our case the local machine acts as the server that contains TestPage.html and HelloWorld.xap, but you can copy these two files to a real remote HTTP server. Now that you have your Silverlight application published, either locally or on a real server, you can request the TestPage.html from a browser. It references the HelloWorld.xap file so the browser will run the Silverlight plugin which will download the XAP file. When the file is downloaded, Silverlight will extract it and read the AppManifest.xaml file. It contains information which DLL Silverlight needs to load and which class to instantiate first (by default it's the App class). Once an instance of the App class is created, the Silverlight application starts to execute.
At this point you should feel comfortable with XAML and C#, and using both in your Silverlight projects. You also have the big picture of what happens when you hit F5 - from compiling your code to executing it by the Silverlight plugin.
The source code of the modified "Hello World" application (written in C# without using XAML): HelloWorldWithoutXaml.zip