Building Avalon Apps: Basics and Behind-the-scenes. Part 1
I thought it would be interesting to walk through building some very basic Avalon applications. Some topics I want to introduce include Avalon’s application model; the interplay between declarative XAML markup and imperative CLR code-behind; what happens at build time; and some behind-the-scenes details at runtime.
The WinFX Beta 1 RC bits
To follow along you’ll need to install the WinFX Beta 1 RC Runtimes and SDK on XP or Windows Server 2003 if you haven’t already.
Although Avalon and Indigo are each parts of WinFX (i.e. the Windows Frameworks), their assemblies are installed to slightly different destinations in the Beta 1 RC. For instance, Indigo’s System.ServiceModel.dll can be found in %WINDIR%\Microsoft.NET\Framework\v2.0.50215. But the Avalon runtimes (WindowsBase.dll, PresentationCore.dll and PresentationFramework.dll, etc) can be found in %WINDIR%\Microsoft.NET\Windows\v6.0.4030. This separation may become clearer on Longhorn but it suggests that Indigo is part of the .NET Framework whereas Avalon is part of WinFX proper. This is supported by the following table:
Assembly |
AssemblyProductAttribute |
AssemblyVersion |
AssemblyVersion of .NET F/W Referenced |
System.ServiceModel |
Microsoft(R) .NET Framework |
2.0.0.0 |
2.0.0.0 |
WindowsBase |
Microsoft (R) Windows (R) Operating System |
6.0.4030.0 |
2.0.0.0 |
PresentationCore |
Microsoft (R) Windows (R) Operating System |
6.0.4030.0 |
2.0.0.0 |
PresentationFramework |
Microsoft (R) Windows (R) Operating System |
6.0.4030.0 |
2.0.0.0 |
Another of Avalon’s assemblies is PresentationBuildTasks.dll which contains the MSBuild Tasks which process XAML into CLR source code files and into a binary representation called BAML.
The first two applications will use command-line builds; I’ll get to Visual Studio 2005 later.
Command-line build 1: C#-only Application
Let’s begin by building an Avalon application entirely in C#. Create a folder called C#-only App and, in it, create a new text file called App.cs. Add the following code to the file:
using System;
class EntryPoint
{
[STAThread]
static void Main() {}
}
So far there is nothing to distinguish this from a regular .NET application. It has a UI thread and an entry point. Now add a using statement for System.Windows. Notice the subtle difference here: if we were writing a Windows Forms application we would use System.Windows.Forms. Next, in Main, instantiate a new Application (actually System.Windows.Application) and call its Run method. Your file should now look like this:
using System;
using System.Windows;
class EntryPoint
{
[STAThread]
static void Main()
{
Application app = new Application();
app.Run();
}
}
If we build and run this, we’ll have a running Avalon application but it will have no way of interacting with the user (unless you count Task Manager!). So let’s have the app show a window. In Main, instantiate a new Window (System.Windows.Window) and call its Show method. Your Main method should look like this:
static void Main()
{
Application app = new Application();
(new Window()).Show();
app.Run();
}
We’ll be using the Microsoft Build Engine (MSBuild) to build our application, so create a project file called App.csproj in your application folder. Inside it put the minimum contents to perform an Avalon build:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AssemblyName>App</AssemblyName>
<OutputPath>.\</OutputPath>
<OutputType>winexe</OutputType>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Compile Include="App.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
The only thing of note, the only part which is different from a normal C# MSBuild project, is the set of assemblies referenced (the ones we talked about earlier).
Now, to build the application from a command-prompt choose Start – All Programs – WinFX SDK – Debug Build Environment and at the prompt change directory to your C#-only App folder. Issue the command msbuild. Confirm that the build succeeded.
Run App.exe and note the default window which is shown. Now, to customize the window a little. Add a new class to App.cs called MyWindow which derives from Window. In the class’s constructor set the window’s Text to “Hello from MyWindow”.
class MyWindow : Window
{
internal MyWindow()
{
this.Text = "Hello from MyWindow";
}
}
Change the code in Main so it instantiates a MyWindow instead of a Window. Build and run again and note the window’s caption is the value you set the Text property to.
So far there’s nothing to distinguish this as an Avalon window so let’s put some Avalon controls inside it. Add a using statement for System.Windows.Controls. In MyWindow’s constructor, declare an object reference and assign a new Button (System.Windows.Controls.Button) to it. Cast the object to a ContentControl and set its Content to “Some text content”. Finally set the MyWindow’s Content to the object.
internal MyWindow()
{
this.Text = "Hello from MyWindow";
object o = new Button();
(o as ContentControl).Content = "Some text content";
this.Content = o;
}
Notice how both the Button and the MyWindow have a Content property of type object. This is because both derive from System.Windows.Controls.ContentControl. This idea that there is no constraint on what can be contained within a control is Avalon’s evolution of text content. In the above code the Button’s content is still plain old text but it could be any object whatsoever (including the root of a tree of content), e.g. a Shape, a Control, a Panel or a business object. The MyWindow’s content is a Button, but would normally be a Panel of some kind inside which the remainder of the window’s content would be laid out.
I made the variable o have type object to show that the Content property of the MyWindow is of type object. But this also enables us to instantiate any ContentControl and assign it to o. Try instantiating a CheckBox instead of a Button.
Build and run. Notice that the Button is filling the window. This is because the default behavior for Buttons is to stretch to fill their container. There are many rules around layout which I’ll cover in a later post. For now, though, cast the Button object to a FrameworkElement and set its horizontal and vertical alignment to Center and, so that we can see it clearly, set its LayoutTransform to new ScaleTransform(5,5) . You’ll also need to add a using statement for System.Windows.Media which is the namespace containing the ScaleTransform class. Your entire code file should finally look like this:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
class EntryPoint
{
[STAThread]
static void Main()
{
Application app = new Application();
(new MyWindow()).Show();
app.Run();
}
}
class MyWindow : Window
{
internal MyWindow()
{
this.Text = "Hello from MyWindow";
object o = new Button();
(o as ContentControl).Content = "Some text content";
(o as FrameworkElement).HorizontalAlignment = HorizontalAlignment.Center;
(o as FrameworkElement).VerticalAlignment = VerticalAlignment.Center;
(o as FrameworkElement).LayoutTransform = new ScaleTransform(5,5);
this.Content = o;
}
}
The casts are there only to highlight which classes define the various properties. If you like you can change o’s type to Button and remove all the casts. Build and run and notice the difference to the Button’s layout and scale transformation.
So far the application consists exclusively of imperative code which would arguably be more suitable in the form of declarative XAML markup. In the next post I’ll look at a XAML-only application (a NavigationApplication in fact) and after that we’ll mix the two modes and begin to use Visual Studio 2005.