Inside XAML by Ian Griffiths,

来源:互联网 发布:line交友软件 编辑:程序博客网 时间:2024/06/06 20:25
 

Inside XAML

by Ian Griffiths, co-author of Mastering Visual Studio .NET
01/19/2004

One of Longhorn's most interesting technologies for developers is its new XML-based markup language, codenamed XAML (short for eXtensible Application Markup Language, and pronounced "Zammel"). User interfaces in Longhorn applications are typically built using XAML. In this article, we look at how XAML relates to the underlying support provided by WinFX.

XAML user interfaces are built in much the same way as HTML web user interfaces -- you simply create a document with tags for each of the user interface elements you require. Here is a simple XAML user interface:

<FlowPanel xmlns="http://schemas.microsoft.com/2003/xaml">    <Text>Hello World</Text>    <Button>Click me!</Button></FlowPanel>

This particular example displays a Text element and a Button element, using a FlowPanel to arrange them on screen. These are all classes defined by Avalon, WinFX's user interface framework. The Text and Button elements are self-explanatory. The FlowPanel is an example of a panel. Panels are used to arrange elements according to a particular layout approach. The FlowPanel arranges elements on screen in the same way that text is typically formatted -- elements "flow" through the available space from left to right, moving onto the next line when no more space is left on the current line. The results are shown below. (The first picture shows a window wide enough to contain both elements on one line. The second shows what happens when the window is not wide enough -- the FlowPanel splits the elements across two lines.) Avalon offers a variety of panel types for different layout techniques, including fixed layout, docking, and column-based text flow.

Some text and a button Some text and a wrapped button

Why a New Markup Language?

You might be wondering why Microsoft decided to invent a brand-new markup language for building user interfaces, rather than using HTML or SVG. One of the reasons is functionality -- Avalon provides many sophisticated user interface features that are not available in HTML, such as scaling and rotation of both text and graphics, and animation. Also, HTML has been developed primarily for use on the Web, whereas XAML's principal target is applications that run directly on Windows (i.e., "rich clients" or "smart clients"). But this still leaves the question of why Microsoft didn't use SVG (Scalable Vector Graphics -- a graphically rich, XML-based markup language.) After all, SVG addresses many of HTML's shortcomings as a rich-client markup language. However, the most powerful reason for devising a new markup language is the very close relationship between elements in a XAML file, and objects at runtime. Unlike any previous markup languages, XAML is designed to integrate directly with WinFX.

XAML and Objects

Each element in a XAML file will result in a corresponding object being created at runtime. In the previous example, the running program will have three objects, of the types FlowPanel, Text, and Button. These classes are part of the Avalon class libraries. The XAML compiler knows which class libraries to use because of the namespace declaration -- the http://schemas.microsoft.com/2003/xaml XML namespace is recognized by the XAML compiler as meaning that Avalon classes are to be used. You can also define custom namespaces to use class libraries. (In principle, you can use any .NET class from XAML. Don Box wrote an article showing that you can even write console applications in XAML, if you want.)

Learning Lab TigerO'Reilly Learning Lab's .NET Certificate Series -- New! Learn .NET programming skills and earn a .NET Programming Certificate from the University of Illinois Office of Continuing Education. The .NET Certificate Series is comprised of three courses that give you the foundation you need to do .NET programming well. The courses are: Learn XML; Learn Object-Oriented Programming Using Java; and Learn C#. Enroll now in all three courses and save over $500.

XAML files are usually compiled rather than being parsed at runtime (although runtime parsing is available it you really need it.) When you build a XAML-based project, the XAML compiler generates a class for each XAML file. These classes contain code to create the objects specified in the XAML. If you are curious, it is possible to find this generated code. Using the Longhorn-enabled version of Visual Studio .NET Whidbey that was given out at the 2003 PDC (Professional Developers Conference), you can use one of the Longhorn Application templates to create a new XAML-based project. When you compile the project, the XAML compiler will generate a temporary source file for each XAML file. These files are created in the obj/Debug or obj/Release subdirectories. For example, if the XAML in the example above is placed in a file called MyPanel.xaml, building the project will cause a temporary file, obj/Debug/MyPanel.g.cs, to be produced. (If you use Visual Basic .NET, the file extension will be .vb instead of .cs.)

If you look inside of this generated file, you will find a class definition. For each XAML file, the XAML compiler builds a class derived from the class for the root element of the file. In the example shown earlier, the root element was a FlowPanel, so the generated class derives from FlowPanel:

public partial class MyPanel : MSAvalon.Windows.Controls.FlowPanel { ... }

Inside of the class's initialization code, it creates the other elements specified in the XAML:

MSAvalon.Windows.Controls.FlowPanel _FlowPanel_1_ = this;((MSAvalon.Windows.Serialization.ILoaded)(_FlowPanel_1_))    .DeferLoad();MSAvalon.Windows.Controls.Text _Text_2_ =     new MSAvalon.Windows.Controls.Text();((MSAvalon.Windows.Serialization.ILoaded)(_Text_2_))    .DeferLoad();((MSAvalon.Windows.Serialization.IAddChild)(_FlowPanel_1_))    .AddChild(_Text_2_);((MSAvalon.Windows.Serialization.IAddChild)(_Text_2_))    .AddText("Hello World");((MSAvalon.Windows.Serialization.ILoaded)(_Text_2_))    .EndDeferLoad();MSAvalon.Windows.Controls.Button _Button_3_ =     new MSAvalon.Windows.Controls.Button();((MSAvalon.Windows.Serialization.ILoaded)(_Button_3_))    .DeferLoad();((MSAvalon.Windows.Serialization.IAddChild)(_FlowPanel_1_))    .AddChild(_Button_3_);((MSAvalon.Windows.Serialization.IAddChild)(_Button_3_))    .AddText("Click me!");((MSAvalon.Windows.Serialization.ILoaded)(_Button_3_))    .EndDeferLoad();((MSAvalon.Windows.Serialization.ILoaded)(_FlowPanel_1_))    .EndDeferLoad();

The lines where the Text and Button objects are created are highlighted in bold. The remaining code simply sets the text of the two elements, and then makes them children of the FlowPanel, so that the tree structure of the original XAML is reflected at runtime. Note that all of this code is generated at compile time -- the XAML itself is not needed at runtime. (Having said that, if you want to generate user interfaces dynamically, it is not necessary to go through this compilation step -- it is also possible to parse XAML at runtime. WinFX provides an API for converting uncompiled XAML directly into objects.)

Properties

As well as creating objects, XAML also allows properties to be set on those objects. For example, we modify the line that creates the Button thus:

<Button Background="Red">Click me!</Button>

This indicates that the Button object's Background property should be set. The XAML compiler generates the appropriate code to set the property:

Button_3_.Background =     new MSAvalon.Windows.Media.SolidColorBrush(      MSAvalon.Windows.Media.Color.FromARGB(255, 255, 0, 0)                                              );

If you're wondering how the XAML compiler is mapping from the string Red to the code above, it uses a technology that has been around since the first version of .NET shipped: type converters. Type converters are a part of the .NET design-time environment, and are used to convert between the strings displayed in the VS.NET Properties window, and the actual values of objects' properties. Type converters can also generate code to initialize these properties -- VS.NET uses them to create the code in the InitializeComponent methods in Windows Forms applications. The XAML compiler simply uses this existing infrastructure to convert attribute string values into initialization code.

Complex Properties

Not all properties can be represented as strings -- some have an internal structure consisting of many nested objects. XAML supports a special syntax for setting these so-called complex properties. Instead of setting properties using attributes, they can instead be set using child elements. To indicate that a XAML element represents a complex property rather than a normal child object, the element name must consist of the name of the parent element followed by a "." and then the property name, as shown in this example:

<Button>  <Button.Background>    <LinearGradientBrush>      <LinearGradientBrush.GradientStops>        <GradientStopCollection>          <GradientStop Color="Red" Offset="0" />          <GradientStop Color="Magenta" Offset="0.25"/>          <GradientStop Color="Blue" Offset="0.5"/>          <GradientStop Color="White" Offset="1"/>        </GradientStopCollection>      </LinearGradientBrush.GradientStops>    </LinearGradientBrush>  </Button.Background>  Click me!</Button>

Rather than relying on the usual type converter mechanism to convert a string to a brush, this example explicitly creates a brush in markup. In this case, we are creating a rather more complex brush -- a LinearGradientBrush with a number of fill stages. This example shows two complex properties, one nested inside of the other. The <Button.Background> element sets the button's background property, but inside of this, the <LinearGradientBrush.GradientStops> element sets the linear gradient brush's GradientStops property. The syntax for complex properties works in exactly the same way as for any other XAML -- it allows trees of objects to be built up. The only difference is that these are then assigned as properties of the elements to which they are applied rather than becoming children of those elements. The results are shown below:

A button with a gradient fill background

Adding Code

As we have seen, the XAML compiler generates a class for each XAML file. The class derives from the type of the root element of the XAML file, and contains code to create all of the child elements, as shown above. However, this generated code will not be sufficient to create a functional UI. Most user interfaces don't just present information -- they typically need to be able to respond to user input as well. You will therefore usually want to add code to provide the user interface's behavior.

It is possible to put code into the XAML file itself. You can add a <Code> element in the Definition namespace. (By convention, the Definition namespace is mapped to the def namespace prefix, so this element usually appears as <def:Code>.) You can then place source code directly inside of this element, and the XAML compiler will add it to the generated source file. You will normally place this inside of a CDATA section, as the following example shows:

<FlowPanel xmlns="http://schemas.microsoft.com/2003/xaml"    xmlns:def="Definition">    <Text>Hello World</Text>    <Button>Click me!</Button>    <def:Code>      <![CDATA[        // Will be added to generated source file        public string Hello()        {          return "Hello!";        }      ]]>    </def:Code></FlowPanel>

However, as anyone who has done much work with building dynamic web pages will know, intermingling code with markup in a single source file is a recipe for unmaintainability. It is much better to keep the visual design of the user interface separate from the code that will determine the UI's behavior. Fortunately, XAML makes it easy to separate the code from the markup -- it supports a style of coding that will be familiar if you have used ASP.NET's "code-behind" feature. (It will be even more familiar if you are familiar with the "code-beside" technique introduced in the Whidbey version of ASP.NET.) As you can see from the example above, the class that the XAML compiler generates is declared with the partial keyword. This indicates to the C# compiler that the class definition may be spread across multiple source files, and that this declaration only contains a part of the definition. This enables us to augment the class by placing more members in another source file -- the C# compiler will build a class that is a sum of all of the partial class definitions it is given for that class.

public partial class MyPanel{  public string Hello()  {    return "Hello!";  }}

When you create a XAML project in Visual Studio .NET, each XAML file you add to the project will automatically have a corresponding source file containing a partial class definition, allowing you to add code to the code that will be generated from the XAML. You can see these code-behind files by clicking the Show All Files button in the Solution Explorer. Note that if you specify a base class in the code-behind file, it must match the base class of the generated code (i.e. the root element of the XAML file) -- the C# compiler will complain if conflicting base classes are specified.

When writing the code for a XAML file, you will of course want to be able to access the objects defined by the markup in order to control the UI. This turns out to be extremely straightforward: all you have to do is annotate the objects you would like to be able to access with an ID attribute in the markup, like so:

<Text ID="textElem">Hello World</Text>

Having done this, the XAML compiler will make the object available as a member variable of the class, using the name specified in the ID attribute. For example, you could modify the text in the element above thus:

textElem.TextRange.Text = "Foo";

You will not usually write this kind of code in public methods like the example above. Most of the code in a code-behind file is in event handler methods.

Handling Events

The main reason for adding code to a XAML page is to handle events -- either input from the user, or significant events in the lifetime of the user interface. It is straightforward to indicate to the XAML compiler that code in our code-behind files should be called when certain events are raised. We simply add an attribute to the element for which we wish to handle an event. The attribute's name should be the event name, and the value should be the name of the handler method in the code-behind. For example, we can handle button clicks like so:

<Button Click="OnClick">Click me!</Button>

This will cause the XAML compiler to generate code that attaches the OnClick function as an event handler. It uses standard .NET event handling to do this, e.g.:

_Button_4_.Click +=     new MSAvalon.Windows.Controls.ClickEventHandler(this.OnClick);

For this to compile correctly, we must of course supply an appropriate OnClick function in the code-behind file. As with all event handlers in .NET programs, the function's signature must match the event's delegate type. In this case, the button's Click event uses the ClickEventHandler delegate, so we must write a function with a matching signature:

private void OnClick(object sender, ClickEventArgs e){    textElem.TextRange.Text = "Foo";}

Conclusion

XAML is a simple but powerful way of building trees of .NET objects. Because it is based on XML, it is straightforward to create XAML-based markup. This not only makes it easy to build user interfaces by hand, it also makes it relatively straightforward for tools to generate XAML -- in the future, design tools will emerge that are able to export documents and drawings in XAML format. It is also easy to use technologies such as XSLT to transform XML data sources into XAML documents. XAML enables a clean separation of user interface from code through the use of code-behind files, while its close integration with WinFX makes it very easy for code to manipulate the user interface elements defined in the markup.

Ian Griffiths is an independent consultant specializing in medical imaging applications and digital video. He also works as an instructor, teaching courses on .NET for DevelopMentor. Ian holds a degree in computer science from Cambridge University.

原创粉丝点击