Command-line build 2: XAML-only NavigationApplication
In the previous post I showed how to build a very basic C#-only Avalon application. At the end I mentioned that declaring and initializing UI is probably not best done with imperative code. This time I’ll go to the other extreme and declare a very simple application entirely in markup. In future posts I’ll settle for a mixture of markup and code-behind.
If you’re familiar with ASP.NET then you’ll know about markup files and code-behind files. The idea is that UI is defined in markup, and application logic is separated out into the code-behind file. This way, in an idea world, a developer can drop a basic UI into markup, make it functional in the code-behind, then let a designer take the markup and make it beautiful. It also means that the UI and application logic needn’t be compiled at the same time.
Although Windows Forms doesn’t have markup, it does have the InitializeComponent method into which the Forms Designer writes a lot of UI initialization code. This gives some separation of static layout from application logic but, because the UI is initialized in imperative code, it’s not as toolable as markup. It also means the UI and application logic must be compiled together.
XAML is Avalon’s markup. XAML can be used to declare any CLR object and it’s effectively a persistence format for CLR objects. When used with Avalon, XAML is a persistence format for Avalon objects, and Avalon offers far better flexibility and customization than ASP.NET or WinForms. XAML is XML so it is extremely toolable and it can be parsed and/or compiled separately from the application logic, even being loaded dynamically at runtime as a control tree.
With the Beta 1 RC bits, the Application we created in the previous post purely in C# can’t be done purely in XAML (I’m told it will be possible in future bits). However, we can do a NavigationApplication purely in XAML. A navigation application has a similar paradigm to a web application or a Wizard in that it has pages between which the user nagivates and a Journal in which navigation history is remembered.
First we’ll create a file in which to declare the application class. Create a folder called XAML-only NavApp and, in it, create a new text file called NavApp.xaml. Declare an empty NavigationApplication element with a StartupUri of Page1.xaml like this:
<NavigationApplication xmlns="http://schemas.microsoft.com/winfx/avalon/2005" StartupUri="Page1.xaml"/>
In XAML, elements correspond to type names and attributes correspond to property names (unless recognised by XML, e.g. the default XML namespace declaration). What we’re doing here is declaring a partial type deriving from System.Windows.Navigation.NavigationApplication. We’ll shortly be processing this markup through a XAML markup parser. The markup parser will know the NavigationApplication type because we’ve specified a default XML namespace which identifies the Avalon types. I haven’t named the derived type so it’ll get a default name (actually _Application). The markup parser will also assume we want a singleton instance of this class and it will generate the code to instantiate one also. We’ll look more closely at what gets generated from this XAML after we’ve built it.
Next, create Page1.xaml containing a Page element as follows:
<Page xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Text="Hello from Page1">
<Hyperlink NavigateUri="Page2.xaml" Text="Go to Page2"/>
Again the Avalon XML namespace is required. The value of the Page element’s Text attribute will appear in the navigation application’s main window caption when this page is shown. Nested inside the Page element is a TextBlock element which is a control used to display simple text content. The markup parser has a set of rules by which it determines what a child element represents relative to its parent. A child element may be a complex property of its parent (e.g. Button.Background); a member of the collection stored in its parent’s default property; part of its parent’s content tree; or something else.
In this case the TextBlock is interpreted as being the value of the Page’s Child property (which is of type UIElement; and TextBlock is a UIElement). Inside the TextBlock is a Hyperlink element which is interpreted as the TextBlock’s content.
Again, this XAML is declaring a new partial type deriving from Page. Whereas the markup parser will instantiate an application instance for us without being explicitly asked, it does not do so for other classes declared in XAML. However, the application’s StartupUri is set to Page1.xaml which is a fairly explicit request for an instance of our Page-derived class and the markup parser will generate code which will result in one. As I’ll show in a moment, this instance is actually created at runtime by another parser.
Next, create Page2.xaml:
<Page xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Text="Hello from Page2"/>
Now create a project file called NavApp.csproj which contains:
<Reference Include="System" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<ApplicationDefinition Include="NavApp.xaml" />
<Page Include="Page1.xaml" />
<Page Include="Page2.xaml" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />
The NavApp.csproj file contains the same Assembly references as the project file in the previous post, but it also has ApplicationDefinition and Page item types. These item types are built by code inside the PresentationBuildTasks.dll I mentioned in the previous post, and the WinFX targets tell MSBuild about that dll.
Choose Start – All Programs – WinFX SDK – Release Build Environment and change directory to your XAML-only NavApp folder. Issue the command msbuild and confirm that the build succeeded.
If you can resist running the application right away you might be interested to look at the generated intermediate files in your \obj\release folder. The .baml files contain a binary version of the XAML (i.e. tokenized XAML). The .g.cs files are generated C# source files.
When we built our project, the Page XAML files were parsed into a corresponding .g.cs source file and a corresponding .baml file. Inside Page1.g.cs you’ll see that a class called _Page1 has been generated. In this class is logic to load the page’s BAML from a managed resource embedded in the assembly. However, although Connect is called (the purpose which is to give the class the opportunity to wire up any event handlers), InitializeComponent is not, as can be witnessed by setting breakpoints. A class called System.Windows.Navigation.NavigationService is actually used behind the scenes to load the BAML and parse it into an tree of user-interface elements.
Inside NavApp.g.cs (which was generated from NavApp.xaml) the _Application class is defined, deriving from NavigationApplication. And in its constructor the StartupUri is set to the same value we set in markup.
NavApp.Main.g.cs does not correspond to any XAML file but it is generated by virtue of the fact that we are building an application. Our Main method is generated in this class along with a ResourceLoader class which you can see being instantiated in the _Application class. However, the ResourceLoader is now a deprecated mechanism. In the Main method our application singleton instance is created, and it is stored in a private static member by the Application base class constructor. This enables the instance to be accessed via System.Windows.Application.Current as you can see happening in _Page1.MyApplication.
Also during the build process, the generated .g.cs source files are then compiled into an assembly. All that remains of the markup is the BAML which is deserialized into CLR objects at runtime by the System.Windows.Serialization.Parser class. This class can be used to (de)serialize any BAML or XAML at runtime.
If you now run the navigation application you will see the chrome at the top of the window which contains the navigation buttons. The chrome actually belongs to an instance of a NavigationWindow which has been automatically created for us, and our Pages are placed inside that. The Journal is the name for the mechanism which remembers our navigation history which you can see by using the navigation buttons’ drop-down buttons after you have navigated with the hyperlink.
Next time I’ll move into Visual Studio 2005 and combine XAML with code-behind in order to benefit from the advantages of each.