In-Depth

Build a Longhorn App

Longhorn includes significant changes that will affect developers, from how it handles graphics to how it stores data. Learn how to create a simple Longhorn app.

Technology Toolbox: C#, XML

This article is excerpted from chapter 2 of Brent Rector's upcoming book, Introducing Longhorn for Developers [Microsoft Press]. It has been edited for length and format to fit the magazine.

You need to install the Longhorn Software Development Kit (SDK) or the Visual Studio release that supports Longhorn before you can build a Longhorn application. Installing the Longhorn SDK creates a set of Start menu items that you can use to get started. Navigate this path to build Debug versions of your application on a Windows XP 32-bit system: Start | Programs | Microsoft Longhorn SDK | Open Build Environment Window | Windows XP 32-bit Build Environment | Set Windows XP 32-bit Build Environment (Debug).

The primary tool for building a Longhorn application is MSBuild. You can run MSBuild with the help command-line option to get detailed information on its usage:

MSBuild /?

Executing MSBuild without any command-line arguments prompts it to search in the current working directory for a filename that ends with "proj." It builds the project according to the directives in that file when it finds one. You can specify the appropriate project file on the command line when you have more than one project file in the directory:

MSBuild <ProjectName>.proj

MSBuild builds the default target in the project file by default. You can override this and specify the target you want build. For example, you invoke MSBuild to build the target named CleanBuild:

/t:Cleanbuild

I'll show you how to write your first program—Hello World, in the grand tradition of programmers everywhere. First, define the Application object. You do this typically in a file called the application definition file. This HelloWorldApplication.xaml file defines the Application object:

HelloWorldApplication.xaml
<NavigationApplication xmlns=
   "http://schemas.microsoft.com/2003/xaml" 
   StartupUri="HelloWorld.xaml" />

This text asserts that you want to use an instance of the MSAvalon.Windows.Navigation.NavigationApplication class for the Application object. On startup, the application should navigate to and display the user interface (UI) defined in the HelloWorld.xaml file. The HelloWorld.xaml file contains this information:

HelloWorld.xaml
<Border xmlns=
   "http://schemas.microsoft.com/2003/xaml">
   <FlowPanel>
      <SimpleText Foreground="DarkRed" 
      FontSize="14">
      Hello World!</SimpleText>   </FlowPanel>
</Border>

You have the "code" you need for a simple Hello World application. Next, you need a project file that defines how to build the application (see Listing 1).

An MSBuild includes several elements. Some you should be familiar with from the start are Property, Item, and Task. A Property is a key-value pair; its value can originate from an environment variable, from a command-line switch, or from a Property definition in a project file:

<Property OutputDir="bin\" />

You can think of an Item as an array of files. An Item can contain wildcards and can exclude specific files. MSBuild uses the Type attribute of an Item to categorize items:

<Item Type="Compile" Include="*.cs" 
   Exclude="DebugStuff.cs" />

Drill Down on Tasks and Targets
A Task is an atomic unit in the build process. A Task can accept input parameters from Property elements, Item elements, or plain strings. The Name of a Task identifies the build .NET data type required to perform the Task. A Task can emit Items that other Tasks consume. MSBuild includes many Tasks, all of which can be broadly categorized as .NET tool tasks, deployment tasks, or shell tasks.

For example, a Task with a Name of Csc invokes the C# compiler as the build tool, which compiles all Item elements specified in the Sources attribute (which specifies Item elements with a Type of Compile) into an assembly and produces the assembly as an output Item:

<Task Name="Csc" AssemblyName=
   "$(OutputDir)\HelloWorld.exe"
         Sources="@(Compile)" />

A Target is a single logical step in the build process that can perform timestamp analysis. This means that a Target won't run if it's not required. A Target executes one or more Tasks to perform the desired operations:

<Target Name="CopyToServer"
Inputs="$(OutputDir)\HelloWorld.exe"
Outputs="\\DeployServer\$(BuildNum)\HelloWorld.exe"
   DependsOnTargets="Compile">

   <Task Name="Copy" ... />
</Target>

A Condition attribute is roughly equivalent to a simple if statement. A condition can compare two strings or check for the existence of a file or directory. You can apply a condition to any element in a project file. For example, this group of properties is defined only when the Configuration property has the value Debug:

<PropertyGroup Condition=
   " '$(Configuration)'=='Debug' " >
      <Property ... />
      <Property ... />
</PropertyGroup>

An Import is roughly equivalent to a C/C++ #include statement. The contents of the imported project become a part of the importing project logically when you import a project:

<Import Project=
   "$(LAPI)\WindowsApplication.target" />

The terminology is out of the way; let's examine a typical project file that lets you build an executable Longhorn application (see Listing 2).

All project files begin with a root element definition named Project. Its DefaultTargets attribute specifies the names of the targets that the system should build when you don't otherwise specify a target. Listing 2 specifies that the system should build the target named Build by default. Build rules can execute conditionally based on property values.

A project for an application must specify, at a minimum, a value for the Language and TargetName properties. This article's example specifies that the language is C# and that the name of the resulting application should be MyApp. It also assigns a value to the property named DefaultClrNameSpace.

Compiling the XAML File
The build system compiles each XAML file into a managed class definition. By default, the managed class will have the same name as the base filename of the XAML source file. For example, the Markup.xaml file compiles into a definition of a class named Markup. Setting the DefaultClrNameSpace property to IntroLonghorn tells the build system to prefix generated class names with the IntroLonghorn namespace. This prompts the build system to produce a class named IntroLonghorn.Markup for the Markup.xaml definition.

You defined your properties prior to importing other projects, so the rules in the imported projects will use the specified property values. For example, you get the proper build rules for C# applications because you defined the Language property as C#.

The rules in the Build target produce the Longhorn application's executable file. Specifying those build rules in every project file would be tedious and repetitive, but you can use this definition to import a predefined project file named WindowsApplication.target:

<Import Project=
   "$(LAPI)\WindowsApplication.target" />

This imported file contains the standard build rules for building a Windows application, and it (indirectly) defines the target named Build.

The ItemGroup element and its child Item elements define all the parts required to build the application. You must have one Item with a Type of ApplicationDefinition. This Item specifies the file that describes the Application object to use for your application. The Application object is typically an instance of either the MSAvalon.Windows.Application class or the MSAvalon.Windows.Navigation.NavigationApplication class, both of which I describe in the extended version of this chapter online:

<Item Type="ApplicationDefinition"
   Include="MyApp.xaml" />

Each Item with a Type of Pages defines a set of XAML files, as shown here. The build system compiles these XAML definitions into classes that it includes in the resulting assembly:

<Item Type="Pages" 
   Include="HomePage.xaml" />
<Item Type="Pages" 
   Include="DetailPage.xaml" />

Each Item with a Type of Code represents a source file, as shown here. The build system compiles these source files using the appropriate compiler selected by your project's Language property:

<Item Type="Code" 
   Include="DetailPage.xaml.cs"/>

This project might depend on other projects. The build system must compile these dependent projects before it can build this project. You list each such dependent project using an Item with Type of DependentProjects:

<Item Type="DependentProjects" 
   Include=
   "MyDependentAssembly.proj" /> 

Code in this project could use types in a prebuilt assembly, also known as a component assembly. The compiler needs a reference to each assembly to compile code using such component assemblies. You will also need to deploy these component assemblies when you deploy your application. You list each component assembly using an Item with Type of Components:

<Item Type="Components" 
   Include="SomeThirdParty.dll" />

A referenced assembly is somewhat different from a component assembly. In both cases, your code uses types in a prebuilt assembly. However, you don't ship a referenced assembly as part of your application, whereas you do ship a component assembly as part of your application. The build system needs to know this distinction.

You specify an Item with a Type of References to indicate that the compiler must reference the specified assembly at build time, but the assembly will not be part of the application deployment. The build system automatically includes references to standard system assemblies—for example, mscorlib.dll, System.dll, PresentationFramework.dll, and more—but you'll have to add any nonstandard assemblies your application must reference manually:

<Item Type="References" 
   Include="SharedThirdParty.dll" />

Your application might also use resources. An Item with a Type of Resources describes a resource used by the application, as shown here. The build system can embed the resource into the resulting assembly or include it as a standalone file. The build system can also place localizable resources into satellite assemblies:

<Item Type="Resources" 
   Include="Picture1.jpg"
   FileStorage="embedded" 
      Localizable="False"/>
<Item Type="Resources" 
   Include="Picture2.jpg"
   FileStorage="embedded" 
      Localizable="True"/>

You'll also want to build libraries in addition to executable applications (see Listing 3).

Build a Longhorn Document
You aren't restricted to building applications with XAML. You can also use XAML files to create a highly interactive, intelligently rendered, adaptive document for a user to read. In this case, your XAML files collectively represent pages of a document. You can use the MSBuild engine to build such documents (see Listing 4).

It's important to understand what a TargetType of Document really produces. When you build a Document, the build output is a container file, and the build system optimizes the contents of the container for download rather than speed. A container file is an extension of the Windows Structured Storage, also known as DocFile, format. Longhorn container handling provides features that allow rendering of partially downloaded files. This means you don't need the entire container downloaded before the application starts running. When you ask MSBuild to create a container file, it compiles each XAML file into a binary representation of the XML, called binary XAML (BAML). BAML is far more compact than either the original text file or a compiled-to-IL assembly. BAML files download more quickly, but an interpreter must parse them at run time to create instances of the classes described in the file. Therefore, such files are not optimized for speed. The examples so far have generated compiled-to-IL files (also known as CAML files, short for compiled XAML).

So far you've learned how to build the various types of Longhorn applications and components. Now let's look at XAML files in more detail, including what the build system does when it turns an XAML file into a .NET class.

An XAML file is a Class Declaration. The application definition file is the XAML file that defines the class of your application's Application object. However, in general, an XAML document is simply a file that defines a class. The class produced by the XAML definition derives from the class associated with the XML document's root element name. By default, the build system uses the XAML base filename as the generated class name.

The next step is to create an application definition file for a navigation application. Recall that the Item element with Type of ApplicationDefinition specifies the name of the XAML file that defines the Application object. In other words, this element specifies the XAML file that contains the entry point for your application. The Longhorn platform will create an instance of the MSAvalon.Windows.Application-derived type that you define in this file and let it manage the startup, shutdown, and navigation of your application.

This XAML file uses markup to define the Application object for a project:

<NavigationApplication xmlns=
   "http://schemas.microsoft.com/2003/xaml" 

StartupUri="HelloWorld.xaml" />

I expect that most Longhorn applications will be navigation-based applications, so they usually will reuse the standard NavigationApplication object. You can reuse this application definition file for most of your navigation-based applications by changing only the value of the StartupUri attribute.

For example, if the previous application definition resides in the HelloWorldApplication.xaml file and you use the HelloWorld.proj project file listed previously, the build system produces this class declaration:

namespace IntroLonghorn {
	class HelloWorldApplication :
		MSAvalon.Windows.Navigation.NavigationApplication 
			{
				.
				.
				.
			}
}

The namespace results from the DefaultClrNameSpace declaration in the project file, the declared class name is the same as the base filename, and the declared class extends the class represented by the root element in the XAML file.

Next, customize the generated code using attributes. When you declare a root element in an XAML file, you can use attributes on the root element to control the name of the generated class declaration. You can use any of three optional attributes: a namespace prefix definition that associates a prefix with a namespace named Definition, a Language attribute (defined in the Definition namespace) that specifies the programming language used by any inline code in the XAML file, and/or a Class attribute (defined in the Definition namespace) that specifies the name of the generated class. Note that you must define a prefix for the Definition namespace to use the Language and Class attributes. Traditionally, the def prefix is used. Also, the build system does not prefix the name with the DefaultClrNameSpace value when you specify a name containing one or more periods.

For example, change the contents of the HelloWorldApplication.xaml file to this:

<NavigationApplication xmlns="http://schemas.microsoft.com/2003/xaml"
   xmlns:def="Definition"
   def:Class="Special.MyApp"
   def:CodeBehind=
      "HelloWorldApplication.xaml.cs" 
   StartupUri="HelloWorld.xaml" />

The generated class looks like this:

namespace Special {
  class MyApp :
           MSAvalon.Windows.Navigation.NavigationApplication 
      {
         .
         .
         .
      }
}

Nearly all applications will require you to write some code—for example, a click event handler for a button or a virtual method override—in addition to the markup that specifies the UI. Let's look at how to combine application code and markup by creating a non-navigation application. I want to emphasize that there is really no compelling reason to create such an application. You can always create a navigation-based application that never actually navigates to a different page. However, writing such an application requires mixing code and markup in the same class, so it serves as a good example.

Recall that creating a non-navigation application requires you to define a custom class that inherits from MSAvalon.Windows.Application and that overrides the OnStartingUp method. The application definition file declares the application object class that your program uses. Therefore, a non-navigation application must define its overriding OnStartingUp method in the same class.

An application configuration file for a non-navigation application contains the same items as a definition file for a navigation application, with a couple exceptions. The root element is Application instead of NavigationApplication. The file must contain or reference the implementation of the OnStartingUp method for your application class.

Using markup and code to implement a single class requires that you associate a source code file with an XAML file. You will often want to develop some portions of your application by using markup and to develop other parts by using a more traditional programming language. I strongly recommend separating the UI and the logic into individual source files.

Simply add an XAML CodeBehind element (defined in the Definition namespace) to the root element of any XAML file and specify the name of a source code file (also known as the code-behind file). The build engine will compile the XAML declarations into a managed class. The build system also compiles the code-behind file into a managed class declaration. The tricky aspect is that both of these class declarations represent partial declarations of a single class.

This XAML definition produces a non-navigation application class:

<Application xmlns=
   "http://schemas.microsoft.com/2003/xaml"
   xmlns:def="Definition"
   def:Language="C#"
      def:Class=
      "IntroLonghorn.CodeBehindSample"
      def:CodeBehind=
      "CodeBehind.xaml.cs" />

There are two noteworthy aspects to this application definition file: The Language attribute specifies that the code-behind file contains C# source code, and the CodeBehind attribute specifies that the source filename is CodeBehindMySample2.xaml.cs. This is the matching source-behind file (see Listing 5).

When you compile an application, MSBuild produces the EXE file plus two manifest files: the application manifest, *.manifest, and a deployment manifest, *.deploy. You use these manifests when deploying an application or document from a server. First, copy the application, all of its dependencies, and the two manifest files to the appropriate location on your server. Second, edit the deployment manifest so that it points to the location of the application manifest.

You've now mastered the basics. Using the information covered in this article and the additional information you can find online, you can write XAML and compile the application. The full version of this chapter also shows you how to deploy and run the resulting application. The sample app in this article is admittedly simple, but it should give you a strong indication of how Microsoft is targeting developers in Longhorn after providing relatively little for them in more recent operating system releases.

comments powered by Disqus

Featured

Subscribe on YouTube