首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

第19章  XAML(和Camel押韵)

下面是一个合法的Extensible Markup Language(XML)片段:

<Button Foreground="LightSeaGreen" FontSize="24pt">

    Hello, XAML!

</Button>

这3行组成了一个XML element:开始标签(start tag)、结束标签(end tag)和两者中间的内容。这里的element类型是Button。开始标签包含两个attribute说明(specification),其attribute名称是Foreground和FontSize。它们被指定了attribute值,XML规定要把attribute值放在一对单引号或双引号内。在开始标签和结束标签之间是element内容,在本例中,是某种字符数据(character data,这是XML的术语)。

XML被设计成一般目的的标记语言,应用相当广,而Extensible Application Markup Language(XAML)是其中的一个应用。

XAML(发音为“zammel”)是WPF补充的编程界面。你可能也已经预料到,上面的XML片段也是XAML的合法片段。Button是定义在System.Windows.Controls命名空间的类,而Foreground和FontSize都是该类的property。你将“Hello, XAML!”文字指定为此Button对象的Content property。

XAML的设计主要是为了对象的建立与初始化。上面的XAML片段对应着下面这段等价(但是更多字)的C#程序代码:

Button btn = new Button();

btn.Foreground = Brushes.LightSeaGreen;

btn.FontSize = 32;

btn.Content = "Hello, XAML!"

请注意:XAML不需要我们明确指出LightSeaGreen是Brushes类的成员,而且我们可以用字符串 "24pt" 来表示24 points。印刷上的point是1/72英寸,所以24 points对应到32设备无关单位。虽然XML常常会更冗长(而XAML在某些方面更是变本加厉),但XAML常常会比等价的程序代码更精要。

WPF程序窗口的layout常常是面板、控件、element的层次结构。这种层次结构和XAML内部嵌套的element相互辉映:

<StackPanel>

    <Button Foreground="LightSeaGreen" FontSize="24pt">

        Hello, XAML!

    </Button>

    <Ellipse Fill="Brown" Width="200" Height="100" />

    <Button>

        <Image Source="http://www.charlespetzold.com/PetzoldTattoo.jpg"

                 Stretch="None" />

    </Button>

</StackPanel>

在此XAML片段中,StackPanel有3个孩子:一个Button、一个Ellipse和另一个Button。第一个Button具有文字内容。另一个Button具有Image内容。注意,Ellipse和Image没有内容,所以这两个element可以用特殊的XML empty-element语法来表示:将整个end tag用一个斜线取代,此斜线放在start tag中关闭tag的大于符号前面。也请注意,Image element的Stretch attribute被指定为Stretch枚举的某个成员的时候,直接写成员名称(而没有写Stretch枚举类型)。

XAML文件本身常常取代Window派生类的整个构造函数,这种构造函数常常用来进行layout并且设置事件处理函数。事件处理函数本身必须用程序代码编写(例如C# 语言)。然而,如果你可以用数据绑定取代事件处理函数,该绑定通常会写在XAML中。

使用XAML可以将一个应用程序的视觉表现和功能分离。此分离让设计者可以操控XAML文件,建立有吸引力的用户界面,而程序员可以专注于运行时element与控件的交互。可以产生XAML的设计工具已经开始出现在市面上。

即使程序员没有必要和图形设计艺术家合作,Visual Studio也内置了设计工具,可以产生XAML。明显地,设计工具产生XML会比产生C# 程序代码更受欢迎。以前Visual Studio设计工具就是产生Windows Forms的程序代码。设计工具产生程序代码之后还必须读进这些程序代码,这通常要求程序代码遵照某种格式。因此,人类程序员不可以弄乱这些被产生出来的程序。然而,XML被特别地设计成既适合计算机编辑,也适合人类编辑。只要编辑者最后让XAML处于语法正确的状态,就不会有问题。

尽管设计工具可以产生XAML,你身为程序员,还是要学习XAML的语法。最好的学习方式,就是从实践中学习。我相信每个WPF程序员都需要能够流畅地使用XAML,并且可以手工编写XAML,我会告诉你如何做到。

本章到目前为止,展示出来的XAML的片段都是取自某个XAML文件中的一小部分,但它们并不足以独立存在。这里存在相当不明确。Button element是什么?是衬衫钮扣(shirt button)吗?电子按钮(electrical button)?还是选举胸章(campaign button)?XML文件必须相当明确才行,不可以有不清楚的地方。如果两个XML文件使用相同的element名称代表不同的意义,这两种文件必须能够被区分开才行。

为此,我们需要XML命名空间。WPF程序员所建立的一份XAML文件,和衬衫钮扣制造商所建立的XML文件,两者具有不同的命名空间。

你在文件中利用xmlns attribute声明默认的XML命名空间。此命名空间会被应用于声明出现的element以及其下的每个孩子。XML命名空间的名称必须是唯一且一致的,为此常常使用URL作为命名空间。对于WPF的XAML命名空间,此URL是:

http://schemas.microsoft.com/winfx/2006/xaml/presentation

别试着用浏览器打开此URL,你看不到东西的。这只是微软的一个命名空间,用来辨识XAML的诸多element之用(例如Button、StackPanel、Image)。

只要加入xmlns attribute和适当的命名空间,本章开头所展示的XAML片段就可以变成一个功能完整的XAML文件:

<Button xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

        Foreground="LightSeaGreen" FontSize="24pt">

    Hello, XAML!

</Button>

此XAML现在可以被放进一个小文件中,或许通过Notepad或NotepadClone建立此文件,或许在上面加上XML注释来帮助文件的识别。你可以将下面的文件保存到你的硬盘。

XamlButton.xaml

<!-- =============================================

     XamlButton.xaml (c) 2006 by Charles Petzold

     ============================================= -->

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Foreground="LightSeaGreen" FontSize="24pt">

    Hello, XAML!

</Button>

这里使用灰色的背景,表示此文件是本书源代码的一部分,你可以从Microsoft Press的网站下载。如果你不想自己键入,你可以在“Chapter 19”的目录下找到此文件。

不管你如何得到此文件,如果你在操作系统上安装了WinFx .NET扩展(extensions to .NET),或者你的操作系统是Microsoft Vista,你就可以像一般程序一样,只要在Windows Explorer中对它双击,或者你也可以在命令提示下执行它。你将会看到Microsoft Internet Explorer(IE)出现,此按钮将会填满整个IE的客户区(不过上面还是会有一些工具栏和URL地址栏)。工具栏的导览按钮会被disable,因为从这样的内容中,没有地方可以前往。(如果你的操作系统是Microsoft Vista,导览按钮将不会出现在客户区;它们的功能被纳入IE自己的导览按钮中。)

像XamlButton.xaml这样的文件,被称为“松散”XAML或“独立”XAML。.xaml扩展名(file name extension)被关联到PresentationHost.exe程序。只要执行XAML,就会造成PresentationHost.exe执行,此程序负责建立Page类型的对象(此类继承自FrameworkElement,但是某些地方很类似于Window),而此程序又可以被嵌入Internet Explorer。PresentationHost. exe程序还将加载的XAML转成实际的Button对象,并将对象设定成Page的Content property。

如果XAML有错误,IE会告诉你,你可以点击IE的“More information”按钮,这个时候你会看到PresentationHost.exe也存在此stack trace(调用堆栈)中。在此stack trace的许多方法中,你可以找出一个特定的静态方法,名为XamlReader.Load,属于System.Windows.Markup命名空间。正是此方法将XAML转成对象,我稍后会告诉你如何使用它。

除了从你自己的硬盘直接执行XamlButton.xaml,你也可以将此文件放在你的网站上,从那里执行。然而,你可能需要为.xaml扩展名注册MIME类型。在某些服务器上,你可以将下面这行加入.htaccess文件中:

AddType application/xaml+xml xaml

下面是另一个“独立”的XAML文件,建立具有3个孩子的StackPanel,这3个孩子分别为Button、Ellipse、ListBox。

XamlStackPanel.xaml

<!-- =================================================

     XamlStackPanel.xaml (c) 2006 by Charles Petzold

     ================================================= -->

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

    <Button HorizontalAlignment="Center" Margin="24">

        Hello, XAML!

    </Button>

    <Ellipse Width="200" Height="100" Margin="24"

        Stroke="Red" StrokeThickness="10" />

    <ListBox Width="100" Height="100" Margin="24">

        <ListBoxItem>Sunday</ListBoxItem>

        <ListBoxItem>Monday</ListBoxItem>

        <ListBoxItem>Tuesday</ListBoxItem>

        <ListBoxItem>Wednesday</ListBoxItem>

        <ListBoxItem>Thursday</ListBoxItem>

        <ListBoxItem>Friday</ListBoxItem>

        <ListBoxItem>Saturday</ListBoxItem>

    </ListBox>

</StackPanel>

XML文件只能够有一个root element,在此文件中,root element是StackPanel。StackPanel的“开始tag”和“结束tag”之间,是StackPanel的内容(它的3个孩子)。Button element和你以前看过的相当类似。此Ellipse element包含5个attribute(对应Ellipse类的property),但不具备content,所以它使用empty-element的语法(将“开始tag”和“结束tag”合并成一个)。ListBox element具有7个孩子,都是ListBoxItem element。每个ListBoxItem的内容是文字字符串。

一般来说,XAML文件代表完整的element树。当PresentationHost.exe加载一个XAML文件,不仅把树中每个element创建出来并初始化,而且将这些element组成视觉树。

当执行这些独立的XAML文件,你可能注意到IE的标题栏会显示文件的路径。在实际的应用程序中,你可能想控制这里显示的文字。你可以让一个root element变成一个Page,设定WindowTitle property,并让StackPanel成为Page的孩子,如同下面的独立XAML文件的做法。

XamlPage.xaml

<!-- ===========================================

     XamlPage.xaml (c) 2006 by Charles Petzold

     =========================================== -->

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    WindowTitle="Xaml Page">

    <StackPanel>

        <Button HorizontalAlignment="Center" Margin="24">

            Hello, XAML!

        </Button>

        <Ellipse Width="200" Height="100" Margin="24"

                    Stroke="Red" StrokeThickness="10" />

        <ListBox Width="100" Height="100" Margin="24">

            <ListBoxItem>Sunday</ListBoxItem>

            <ListBoxItem>Monday</ListBoxItem>

            <ListBoxItem>Tuesday</ListBoxItem>

            <ListBoxItem>Wednesday</ListBoxItem>

            <ListBoxItem>Thursday</ListBoxItem>

            <ListBoxItem>Friday</ListBoxItem>

            <ListBoxItem>Saturday</ListBoxItem>

        </ListBox>

    </StackPanel>

</Page>

你可能会想要尝试将Window当作一个独立XAML文件的root element。这是行不通的,因为PresentationHost.exe想让root element成为“某东西”的孩子,而Window对象不能当任何东西的孩子。独立XAML文件的root element可以是继承自FrameworkElement的任何类,但是不可以是Window。

假设你有一个C# 程序,定义一个字符串变量(例如strXaml),此变量内容是一个小而完整的XAML文件:

string strXaml =

    "<Button xmlns='http://schemas.microsoft.com/winfx/2006/presentation'" +

    "          Foreground='LightSeaGreen' FontSize='24pt'>" +

    "     Click me!" +

    "</Button>";

为了让字符串更具有可读性,我使用单引号而非双引号来表示attribute的值。你可以写一个程序,解析(parse)此字符串,以建立并初始化一个Button对象吗?你会使用许多reflection,并且根据用来设定Foreground与FontSize property的数据,做出某些假设。这样的parser做法不难想象,所以如果说已经存在这样的parser,应该也不会让你觉得惊讶。System.Windows.Markup命名空间包含XamlReader类,它具有一个名为Load的静态方法,可以解析XAML,并将它转成一个初始化过的对象。(除此之外,静态的XamlWriter.Save方法做的事刚好是反方向,从对象产生XAML。)

WPF程序可以使用XamlReader.Load将一段XAML转成一个对象。如果此XAML的根element具有子element,这些element会一并被转换,并放在视觉树中,符合XAML的层次结构。

要使用XamlReader.Load,你当然要加入System.Windows.Markup命名空间,但是你也需要引用System.Xml.dll组件(assembly),它包含XML相关的类,这些类显然是XamlReader.Load需要用到的。不幸的是,XamlReader.Load不能直接接受字符串作为参数。否则,你就可以直接传递一些XAML到此方法,并将结果转换成所需要的对象类型:

Button btn = (Button) XamlReader.Load(strXaml); // Won’t work!

XamlReader.Load需要一个Stream对象,或者一个XmlReader对象。这里有一个做法,是利用MemoryStream对象。(你需要将System.IO namespace命名空间加入程序代码中。)StreamWriter将字符串写进MemoryStream中,然后将MemoryStream传入XamlReader.Load中:

MemoryStream memory = new MemoryStream(strXaml.Length);

StreamWriter writer = new StreamWriter(memory);

writer.Write(strXaml);

writer.Flush();

memory.Seek(0, SeekOrigin.Begin);

object obj = XamlReader.Load(memory);

下面是比较流畅的做法,需要使用System.Xml与System.IO命名空间:

StringReader strreader = new StringReader(strXaml);

XmlTextReader xmlreader = new XmlTextReader(strreader);

object obj = XamlReader.Load(xmlreader);

你可以使用下面这样的做法,这条语句相当难以阅读:

object obj = XamlReader.Load(new XmlTextReader(new StringReader(strXaml)));

下面的程序定义了和上面一样的strXaml字符串,然后将这一短的XAML文件转成一个对象,并将对象设定成为窗口的Content property:

LoadEmbeddedXaml.cs

//-------------------------------------------------

// LoadEmbeddedXaml.cs (c) 2006 by Charles Petzold

//-------------------------------------------------

using System;

using System.IO;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Markup;

using System.Xml;

namespace Petzold.LoadEmbeddedXaml

{

    public class LoadEmbeddedXaml : Window

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new LoadEmbeddedXaml());

        }

        public LoadEmbeddedXaml()

        {

            Title = "Load Embedded Xaml";

            string strXaml =

                "<Button xmlns='http://schemas.microsoft.com/" +

                                             "winfx/2006/xaml/presentation'" +

                "         Foreground='LightSeaGreen' FontSize='24pt'>" +

                "     Click me!" +

                "</Button>";

            StringReader strreader = new StringReader(strXaml);

            XmlTextReader xmlreader = new XmlTextReader(strreader);

            object obj = XamlReader.Load(xmlreader);

            Content = obj;

        }

    }

}

因为此程序刚好知道从XamlReader.Load返回的对象是Button,所以就将它转成Button:

Button btn = (Button) XamlReader.Load(xmlreader);

然后此程序会将事件处理函数设置好:

btn.Click += ButtonOnClick;

如果你的程序包含明确的代码建立并初始化此Button的话,你可以对它做任何事。

当然,将XAML定义成字符串变量,有一点诡异。或许比较好的做法是让XAML成为运行时从程序可执行文件的资源中加载的对象。

我们从一个空的工程开始(就和平常一样),将此工程命名为LoadXamlResource。加入对System.Xml组件和其他WPF组件的引用。从“Project”菜单选取“Add New Item”(或者用鼠标右键点击工程名称,然后选取“Add New Item”)。选择一个XML File的template,文件名为LoadXamlResource.xml。(我要向你展示,这里扩展名为.xml会比扩展名为.xaml更容易。如果你使用.xaml扩展名,Visual Studio会想要加载XAML设计工具,并做出一些目前不太适合的假设。)下面是XML文件。

LoadXamlResource.xml

<!-- ==================================================

     LoadXamlResource.xml (c) 2006 by Charles Petzold

     ================================================== -->

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

    <Button Name="MyButton"

            HorizontalAlignment="Center"

            Margin="24">

        Hello, XAML!

    </Button>

    <Ellipse Width="200"

        Height="100"

        Margin="24"

        Stroke="Red"

        StrokeThickness="10" />

    <ListBox Width="100"

            Height="100"

            Margin="24">

        <ListBoxItem>Sunday</ListBoxItem>

        <ListBoxItem>Monday</ListBoxItem>

        <ListBoxItem>Tuesday</ListBoxItem>

        <ListBoxItem>Wednesday</ListBoxItem>

        <ListBoxItem>Thursday</ListBoxItem>

        <ListBoxItem>Friday</ListBoxItem>

        <ListBoxItem>Saturday</ListBoxItem>

    </ListBox>

</StackPanel>

如你所见,此文件非常类似于独立的XamlStack.xaml文件。最大的区别是,我在此为Button对象加入了Name attribute。此Name property是由FrameworkElement所定义的。

很重要的是:鼠标右键点击Visual Studio中的LoadXamlResource.xml文件,选择Properties,确定Build Action被设定成Resource,否则此程序无法将它视为资源而加载。

LoadXamlResource工程也包含一个看起来更正常的C# 文件,这里有一个继承自Window的类。

LoadXamlResource.cs

//-------------------------------------------------

// LoadXamlResource.cs (c) 2006 by Charles Petzold

//-------------------------------------------------

using System;

using System.IO;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Markup;

namespace Petzold.LoadXamlResource

{

    public class LoadXamlResource : Window

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new LoadXamlResource());

        }

        public LoadXamlResource()

        {

            Title = "Load Xaml Resource";

            Uri uri = new Uri("pack://application:,,,/LoadXamlResource.xml");

            Stream stream = Application.GetResourceStream(uri).Stream;

            FrameworkElement el = XamlReader.Load(stream) as FrameworkElement;

            Content = el;

            Button btn = el.FindName("MyButton") as Button;

            if (btn != null)

                btn.Click += ButtonOnClick;

        }

        void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            MessageBox.Show("The button labeled '" +

                               (args.Source as Button).Content +

                               "' has been clicked");

        }

    }

}

构造函数为此XML资源建立一个Uri对象,然后使用静态的Application.GetResourceStream property,取得一个StreamResourceInfo对象。StreamResourceInfo包含一个名为Stream的property,此property返回一个Stream对象,正是此资源。将此Stream对象用作XamlReader.Load的参数,得到一个StackPanel对象,指定给窗口的Content property。

一旦XAML所转成的对象变成窗口视觉树的一部分,就可以使用FindName方法在树中找出特定名称的element,也就是此Button。然后此程序就会为它设置事件处理函数,或做些别的事。想为运行时加载的XAML设置事件处理函数,这或许是最直接的做法。

下面是一个小变化,此工程名为LoadXamlWindow。就像是前面的工程一样,此XML文件必须将Build Action设定为Resource:

LoadXamlWindow.xml

<!-- ================================================

     LoadXamlWindow.xml (c) 2006 by Charles Petzold

     ================================================ -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Title="Load Xaml Window"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

    <StackPanel>

        <Button HorizontalAlignment="Center"

                  Margin="24">

            Hello, XAML!

        </Button>

        <Ellipse Width="200"

                   Height="100"

                   Margin="24"

                   Stroke="Red"

                   StrokeThickness="10" />

        <ListBox Width="100"

                   Height="100"

                   Margin="24">

            <ListBoxItem>Sunday</ListBoxItem>

            <ListBoxItem>Monday</ListBoxItem>

            <ListBoxItem>Tuesday</ListBoxItem>

            <ListBoxItem>Wednesday</ListBoxItem>

            <ListBoxItem>Thursday</ListBoxItem>

            <ListBoxItem>Friday</ListBoxItem>

            <ListBoxItem>Saturday</ListBoxItem>

        </ListBox>

    </StackPanel>

</Window>

此XAML的root element是Window。请注意,Window开始标签(start tag)的attribute包括了Title、SizeToContent和ResizeMode。其中SizeToContent和ResizeMode的值是各自相关的枚举成员。

独立的XAML文件,其根element不可以是Window,因为PresentationHost.exe希望让被转换的XAML成为某东西的孩子(而Window不可以作为孩子)。幸运的是,下面的程序知道XAML资源是一个Window对象,所以它没有继承自Window,或者直接创建一个Window对象(而是将此XAML资源作为Window对象)。

LoadXamlWindow.cs

//-----------------------------------------------

// LoadXamlWindow.cs (c) 2006 by Charles Petzold

//-----------------------------------------------

using System;

using System.IO;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Markup;

namespace Petzold.LoadXamlWindow

{

    public class LoadXamlWindow

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            Uri uri = new Uri("pack://application:,,,/LoadXamlWindow.xml");

            Stream stream = Application.GetResourceStream(uri).Stream;

            Window win = XamlReader.Load(stream) as Window;

            win.AddHandler(Button.ClickEvent,

                               new RoutedEventHandler(ButtonOnClick));

            app.Run(win);

        }

        static void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            MessageBox.Show("The button labeled '" +

                               (args.Source as Button).Content +

                               "' has been clicked");

        }

    }

}

Main方法建立一个Application对象,加载XAML,然后将XamlReader.Load返回的对象转型成为Window对象。此程序为按钮Click事件设置一个事件处理函数。这里并没有从视觉树中查找按钮,而是调用窗口的AddHandler方法,来设置事件处理函数。最后,Main方法将此Window对象传递给Application的Run方法。

下面的程序包含一个Open File对话框,让你从磁盘加载一个XAML文件。你可以使用此程序,加载本章至今的任何XAML文件(扩展名是.xml)。

LoadXamlFile.cs

//---------------------------------------------

// LoadXamlFile.cs (c) 2006 by Charles Petzold

//---------------------------------------------

using Microsoft.Win32;

using System;

using System.IO;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Markup;

using System.Xml;

namespace Petzold.LoadXamlFile

{

    public class LoadXamlFile : Window

    {

        Frame frame;

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new LoadXamlFile());

        }

        public LoadXamlFile()

        {

            Title = "Load XAML File";

            DockPanel dock = new DockPanel();

            Content = dock;

            // Create button for Open File dialog.

            Button btn = new Button();

            btn.Content = "Open File...";

            btn.Margin = new Thickness(12);

            btn.HorizontalAlignment = HorizontalAlignment.Left;

            btn.Click += ButtonOnClick;

            dock.Children.Add(btn);

            DockPanel.SetDock(btn, Dock.Top);

            // Create Frame for hosting loaded XAML.

            frame = new Frame();

            dock.Children.Add(frame);

        }

        void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            OpenFileDialog dlg = new OpenFileDialog();

            dlg.Filter = "XAML Files (*.xaml)|*.xaml|All files (*.*)|*.*";

            if ((bool)dlg.ShowDialog())

            {

                try

                {

                    // Read file with XmlTextReader.

                    XmlTextReader xmlreader = new XmlTextReader(dlg.FileName);

                    // Convert XAML to object.

                    object obj = XamlReader.Load(xmlreader);

                    // If it's a Window, call Show.

                    if (obj is Window)

                    {

                        Window win = obj as Window;

                        win.Owner = this;

                        win.Show();

                    }

                    // Otherwise, set as Content of Frame.

                    else

                        frame.Content = obj;

                }

                catch (Exception exc)

                {

                    MessageBox.Show(exc.Message, Title);

                }

            }

        }

    }

}

正如你在ButtonOnClick方法中所看到的,从OpenFileDialog取出文件名,会比将XAML当作资源加载更容易一些。文件名可以被直接传递给XmlTextReader构造函数,此对象(XmlTextReader)可以被XamlReader.Load当参数接受。

如果从XamlReader.Load返回的对象是Window的话,此方法有一些特殊的逻辑。它将Window对象的Owner property设定成自己,然后调用Show,彷佛被加载的窗口是一个modeless对话框。(我发现关闭主应用程序窗口,但是XAML加载的窗口依然没关闭,造成应用程序无法结束。当我发现这种情况之后,才加入设定Owner property的程序代码。这样的做法对我来说不太适当。另一种解决方式是设定Application的ShutdownMode property为ShutdownMode. OnMainWindowClose,下一章的XAML Cruncher程序就是这么做的。)

你现在已经看到一些不同的做法,都可以在运行时加载XAML,而且你也知道加载XAML的程序代码如何能够定位树中的各种element,并为它们设置事件处理函数。

然而,在实际的应用程序中,将XAML和你的源代码一起编译,这么做比较常见。毫无疑问,这样更有效率,而且你可以在编译版本的XAML中做某些事,这些事却不能在独立的XAML中进行。其中一些事,就是在XAML中指定事件处理函数的名称。通常事件处理函数

本身位于某个程序代码(procedure code)文件中,但是你也可以将某些C# 程序代码嵌入XAML内。当你将XAML和整个工程一起编译时,是可以这么做的。通常你的工程中,应用程序的每个页面或每个窗口(包括对话框)都会有一个XAML文件,而且每个XAML文件都会有一个关联的程序代码文件(常常称为code behind文件)。但是这只是通则,你也可以在你的工程中使用更多或更少的XAML文件。

你到目前为止所看见的XAML都使用WPF的类和property。但是,XAML并非WPF专用的标记语言。应该把WPF当作是XAML的一种可能的应用方式。XAML也可以被用在WPF以外的其他应用程序框架(比方说,XAML可以搭配Windows Workflow Foundation使用。)

XAML规范定义了数个element和attribute,你可以在任何XAML应用(包括WPF)中使用。这些element和attribute被关联到不同于WPF的命名空间,而如果你想要使用XAML专用的element和attribute(你一定会这么做的),那么你需要将第二个命名空间的声明放在你的XAML文件中。这第二个命名空间的声明引用下面的URL:

http://schemas.microsoft.com/winfx/2006/xaml

这和WPF的URL一样,但是没有presentation路径的部分,因为presentation代表的是WPF。此WPF命名空间的声明将会持续出现在本书所有的XAML文件中:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

XAML专用element与attribute的命名空间习惯上被声明成“x”前缀(prefix):

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

当然,你可以使用任何前缀(但是开头不可以是XML),不一定要用x,不过x已经变成许多XAML文件所采用的惯例了。

(理论上,让XAML文件内默认的命名空间和XAML自己的关联起来,然后用第二个命名空间声明WPF element,这样感觉更加合理。然而,XAML所定义的element和attribute相当少,为了避免XAML文件内有太多前缀,让WPF element的命名空间为默认命名空间,会比较实用。)

在本章中,你会看到Class attribute和Code element的例子,两者都是属于XAML命名空间,而非WPF命名空间。因为XAML命名空间习惯上使用x当前缀,所以Class和Code element出现在XAML文件中,通常会是“x:Class”与“x:Code”,我正是这么称呼它们的。

x:Class attribute只能出现在XAML文件的root element。此attribute只允许会被编译成工程一部分的XAML使用。它不可以出现在松散的XAML或运行时加载的XAML中。x:Class属性看起来类似这样:

x:Class="MyNamespace.MyClassName"

常常,此x:Class attribute会出现在Window的root element,所以此XAML文件的整体结构可能是:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

          x:Class="MyNamespace.MyClassName"

          ... >

    ...

</Window>

这里的MyNamespace命名空间指的是和此应用程序工程有关联的.NET命名空间(也就是CLR命名空间)。你通常会有对应的Window类(用C# 语言写的),具有相同的命名空间和类,定义此类时使用partial关键字:

public namespace MyNamespace

{

    public partial class MyClassName: Window

    {

        ...

    }

}

这就是code-behind文件,因为它包含code(这里的code,常常是事件处理函数,也可能是某些初始化的程序代码),这些code是用来支持定义在XAML文件内的控件和element。此XAML文件和此code-behind文件其实是同一个类的不同部分,Window类通常如此。

再一次,让我们从一个空的工程开始。此次的工程名为CompileXamlWindow。此工程需要两个文件,一个名为CompileXamlWindow.xaml的XAML文件,和一个名为CompileXaml- Window.cs的C# 文件。两个文件都是相同类的不同部分,此类的全名(含命名空间)为Petzold.CompileXamlWindow.CompileXamlWindow。

让我们先建立此XAML文件。在空的工程中,加入一个XML文件的新项目,指定名称为CompileXamlWindow.xaml。Visual Studio会加载设计工具,但是你要试着摆脱设计工具。在源代码窗口的左下角,点击Xaml页,而非点击Design页。

如果你检查CompileXamlWindow.xaml文件的Properties,Build Action应该是Page,如果不是的话,就设定成Page。(稍早在LoadXamlResource和LoadXamlWindow工程中,我请你把XAML文件的扩展名设为.xml,现在我却要你用.xaml当扩展名。其实,使用什么扩展名无所谓,重点在Build Action的设定。在前面的工程中,我们想要让XAML文件变成可执行文件的资源;在现在的项目,我们想要让此文件被编译,而将Build Action设定成Page就会造成此文件被编译。)

CompileXamlWindow.xaml文件类似于LoadXamlWindow.xml文件。第一个大的差异在于此文件包含第二个命名空间x前缀的声明,以及在根element中使用x:Class attribute。我们这里所定义的是一个类,继承自Window,此类的完整名称是Petzold.CompileXamlWindow. CompileXamlWindow。

CompileXamlWindow.xaml

<!-- ====================================================

     CompileXamlWindow.xaml (c) 2006 by Charles Petzold

     ==================================================== -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

          x:Class="Petzold.CompileXamlWindow.CompileXamlWindow"

          Title="Compile XAML Window"

          SizeToContent="WidthAndHeight"

          ResizeMode="CanMinimize">

    <StackPanel>

        <Button HorizontalAlignment="Center"

                Margin="24"

                Click="ButtonOnClick">

            Click the Button

        </Button>

        <Ellipse Name="elips"

            Width="200"

            Height="100"

            Margin="24"

            Stroke="Black"/>

        <ListBox Name="lstbox"

            Width="150"

            Height="150"

            Margin="24"

            SelectionChanged="ListBoxOnSelection" />

    </StackPanel>

</Window>

实际上,你将会看到,此XAML文件定义了一个类,其C# 语法看起来类似下面:

namespace Petzold.CompileXamlWindow

{

    public partial class CompileXamlWindow: Window

    {

        ...

    }

}

Partial关键词表示此CompileXamlWindow类在其他地方还有程序代码。那是在C# code-behind文件中的程序代码。

请注意按钮的XAML element包含了一个Click事件的attribute,并指定事件处理函数的名称为ButtonOnClick。这个事件处理函数在哪里?它将会在CompileXamlWindow类的C# 程序代码部分。ListBox也需要SelectionChanged事件的处理函数。

虽然Ellipse和ListBox都具有Name attribute,其值分别为elips和lstbox。你稍早看到程序要如何利用FindName方法定位树中的element。当你编译XAML的时候,Name attribute扮演着相当重要的角色。它们会变成类的字段,所以用XAML所建立的类,在编译期间,更像是这样:

namespace Petzold.CompileXamlWindow

{

    public partial class CompileXamlWindow: Window

    {

        Ellipse elips;

        ListBox lstbox;

        ...

    }

}

在你所编写的CompileXamlWindow类C# 部分,你可以直接引用这些字段。下面是code-behind文件,包含了CompileXamlWindow类剩下的部分:

CompileXamlWindow.cs

//--------------------------------------------------

// CompileXamlWindow.cs (c) 2006 by Charles Petzold

//--------------------------------------------------

using System;

using System.Reflection;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

namespace Petzold.CompileXamlWindow

{

    public partial class CompileXamlWindow : Window

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new CompileXamlWindow());

        }

        public CompileXamlWindow()

        {

            // Required method call to hook up event handlers and

            // initialize fields.

            InitializeComponent();

            // Fill up the ListBox with brush names.

            foreach (PropertyInfo prop in typeof(Brushes).GetProperties())

                lstbox.Items.Add(prop.Name);

        }

        // Button event handler just displays MessageBox.

        void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            Button btn = sender as Button;

            MessageBox.Show("The button labled '" + btn.Content +

                                 "' has been clicked.");

        }

        // ListBox event handler changes Fill property of Ellipse.

        void ListBoxOnSelection(object sender, SelectionChangedEventArgs args)

        {

            ListBox lstbox = sender as ListBox;

            string strItem = lstbox.SelectedItem as string;

            PropertyInfo prop = typeof(Brushes).GetProperty(strItem);

            elips.Fill = (Brush)prop.GetValue(null, null);

        }

    }

}

CompileXamlWindow类继承自Window,这和平常一样,但是声明也包含了partial关键词。此类具有一个静态的Main方法,这也和平常一样。然而,CompileXamlWindow构造函数一开始会调用InitializeComponent。此方法显然是CompileXamlWindow类的一部分,但是在此文件中却看不到此方法的定义。你很快就会看到此方法。目前你应该要知道此方法具有一些重要的功能,像是设定字段lstbox与elips的值为从XAML建立的ListBox和Ellipse element,以及为Button和ListBox控件设置事件处理函数。

CompileXamlWindow的构造函数没有设定窗口的Title property或其他任何内容,因为这些都是在XAML中处理的。但是它确实需要为list box填入数据。程序代码剩下的部分,是两个事件处理函数。ButtonOnClick处理函数就只是显示出MessageBox,你可能已经试过了。ListBox的SelectionChanged事件处理函数,会改变Ellipse对象的Fill property。虽然此事件处理函数从sender参数获得此ListBox对象,它其实也可以直接取用lstbox字段。你可以删除事件处理函数的第一个语句,程序的作用会一样。

当你编译并且执行此工程时,你会亲眼看到它生效了,这当然是相当重要的目标,但此时你可能也希望看一看它的工作原理。

看看工程目录下的obj子目录,可能是obj下面的Release或Debug子目录(取决于你是用何种方式编译的)。你会看到有一个文件名为CompileXamlWindow.baml。这个扩展名的意思是Binary XAML,发音为“bammel”。这是已经被解析、切割成token并且转成二进制格式的

XAML文件。此文件变成可执行程序的一部分,如同应用程序的资源(resource)一样。

你也会看到一个名为CompileXamlWindow.g.cs的文件,这是产生自XAML的文件(g表示generated)。将它用Notepad或别的文本查看器打开,这就是CompileXamlWindow类的另一部分,它会和CompileXamlWindow.cs文件被编译在一起。靠近类顶端的地方,你会看到lstbox和elips字段的声明。你也会看到InitializeComponent方法,在运行时加载BAML文件,并将它转成element tree。在此文件的底端,有方法会设定lstbox和elips字段,并设置事件处理函数。(有时候Visual Studio显示出的编译错误信息中,会有这些产生出来的文件。这时候你需要在不去编辑被产生文件的情况下解决错误。)

当CompileXamlWindow类的构造函数开始执行,窗口的Content property是null,而且所有的窗口property(例如Title、SizeToWindow与ResizeMode)都是默认值。在调用完InitializeComponent之后,Content是StackPanel,而且其他的property都被设定成XAML文件所指定的值。

只有在你将XAML和程序代码一起编译的情况下,CompileXamlWindow 程序所展示出来的XAML和C# 程序代码之间的关联方式(共享一个类、指定事件处理函数、设定字段),才有可能。当你直接(或间接)调用XamlReader.Load,在运行时加载XAML,你的选择就会比较少。你已经存取过XAML所建立的对象,但是要设定事件处理函数,或将此对象保存为字段,却不是容易的事。

关于XAML,常被问的一个问题是“我能在XAML中使用自己的类吗?”是的!你可以。为了让自定义类(定义在C# 文件)和整个工程一起编译,你只要在XAML文件中加上自定义类的名称声明即可。

假设你有一个自定义控件,名为MyControl,定义在C# 文件中,其CLR命名空间是MyNamespace。你将此C# 文件包含进此工程中,在XAML文件内,你必须先为此CLR命名空间建立一个前缀(prefix)的关联,比方说stuff,声明方式如下:

xmlns:stuff="clr-namespace:MyNamespace"

其中,“clr-namespace”必须是小写,后面要接着一个冒号。(这类似于常见XML声明中的http:部分,在下一章,我们会讨论更多引用外部动态链接库的语法。)此命名空间声明必须出现在第一次引用MyControl之前,或者作为MyControl元素的属性。此MyControl元素需要前置stuff:

<stuff:MyControl ... >

你应该使用什么前缀?(我假设你已经拒绝用“stuff”作为一个一般目的的解决方案。)习惯上使用简短的前缀,但这不是硬性规定。如果你的项目包含来自多个CLR命名空间的源代码,你需要为每个命名空间都设定一个前缀,你可以让前缀类似CLR命名空间的名字,以避免混淆。如果所有的自定义类都在一个命名空间内,常常用src(意思是source code源代码)当作前缀。

让我们建立一个新的工程,名为UseCustomClass。此工程包含一个链接,连到第13章SelectColorFromGrid工程的ColorGridBox.cs文件。此ColorGridBox类的命名空间是Petzold.SelectColorFromGrid,所以为了要在XAML文件中使用此类,你需要下面的命名空间声明:

xmlns:src="clr-namespace:Petzold.SelectColorFromGrid"

下面是UseCustomClass.xaml文件,包含命名空间声明以便用src前缀引用ColorGridBox控件。

UseCustomClass.xaml

<!-- =================================================

     UseCustomClass.xaml (c) 2006 by Charles Petzold

     ================================================= -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:src="clr-namespace:Petzold.SelectColorFromGrid"

        x:Class="Petzold.UseCustomClass.UseCustomClass"

        Title="Use Custom Class"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

    <StackPanel Orientation="Horizontal">

        <Button HorizontalAlignment="Center"

                VerticalAlignment="Center"

                Margin="24">

            Do-nothing button to test tabbing

        </Button>

        <src:ColorGridBox HorizontalAlignment="Center"

                               VerticalAlignment="Center"

                               Margin="24"

                               SelectionChanged="ColorGridBoxOnSelectionChanged" />

        <Button HorizontalAlignment="Center"

                VerticalAlignment="Center"

                Margin="24">

            Do-nothing button to test tabbing

        </Button>

    </StackPanel>

</Window>

Code-behind文件包含Main、对InitializeComponent的调用和ColorGridBox控件的SelectionChanged事件处理函数。

UseCustomClass.cs

//-----------------------------------------------

// UseCustomClass.cs (c) 2006 by Charles Petzold

//-----------------------------------------------

using Petzold.SelectColorFromGrid;

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

namespace Petzold.UseCustomClass

{

    public partial class UseCustomClass : Window

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new UseCustomClass());

        }

        public UseCustomClass()

        {

            InitializeComponent();

        }

        void ColorGridBoxOnSelectionChanged(object sender,

                                                       SelectionChangedEventArgs args)

        {

            ColorGridBox clrbox = args.Source as ColorGridBox;

            Background = (Brush) clrbox.SelectedValue;

        }

    }

}

UseCustomClass.cs文件需要将Petzold.SelectColorFromGrid命名空间加进来(利用using指示符),因为事件处理函数引用了ColorGridBox类。你可以改变该引用,改成只引用ListBox(ColorGridBox继承自ListBox),那么你就不需要使用此using指示符了。将SelectionChanged事件处理函数整个省略掉,是有可能的,可以在XAML中改用数据绑定,但是语法会有一点不一样,所以这部分等到第23章再来讨论。

稍早我提过,一般来说,每个窗口和对话框都会有一个XAML文件和一个对应的code-behind文件。但是不要因此误以为XAML文件不能用来代表Window以外的element。下面的UseCustomXamlClass工程定义了一个自定义类,继承自Button(虽然是很简单的类),完全用XAML来表示。使用XAML来定义自定义类的时候,是通过root element的x:Class attribute,这是此attribute唯一可以出现的地方。下面的XAML文件定义此Button派生类为CenteredButton。而XAML将HorizontalAlignment与VerticalAlignment property设定为Center,并且让按钮有一些边界(margin)。

CenteredButton.xaml

<!-- =================================================

     CenteredButton.xaml (c) 2006 by Charles Petzold

     ================================================= -->

<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        x:Class="Petzold.UseCustomXamlClass.CenteredButton"

        HorizontalAlignment="Center"

        VerticalAlignment="Center"

        Margin="12" />

Code-behind文件是CenteredButton的partial类,实在太简单,以至于连using指示符都不需要。

CenteredButton.cs

//-----------------------------------------------

// CenteredButton.cs (c) 2006 by Charles Petzold

//-----------------------------------------------

namespace Petzold.UseCustomXamlClass

{

    public partial class CenteredButton

    {

        public CenteredButton()

        {

            InitializeComponent();

        }

    }

}

此工程也包含一个继承自Window类的XAML文件。除了正常的x:Class attribute之外,此文件也包含此工程的XML命名空间声明,如此一来,StackPanel可以包含CenteredButton类的5个实例(instance)。

UseCustomXamlClass.xaml

<!-- =====================================================

     UseCustomXamlClass.xaml (c) 2006 by Charles Petzold

     ===================================================== -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:src="clr-namespace:Petzold.UseCustomXamlClass"

        x:Class="Petzold.UseCustomXamlClass.UseCustomXamlClass"

        Title = "Use Custom XAML Class">

    <StackPanel Name="stack">

        <src:CenteredButton>Button A</src:CenteredButton>

        <src:CenteredButton>Button B</src:CenteredButton>

        <src:CenteredButton>Button C</src:CenteredButton>

        <src:CenteredButton>Button D</src:CenteredButton>

        <src:CenteredButton>Button E</src:CenteredButton>

    </StackPanel>

</Window>

请注意,StackPanel具有Name attribute,其值是stack。这里的code-behind文件使用此名称来为StackPanel加入另外5个按钮(总共10个)。

UseCustomXamlClass.cs

//---------------------------------------------------

// UseCustomXamlClass.cs (c) 2006 by Charles Petzold

//---------------------------------------------------

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

namespace Petzold.UseCustomXamlClass

{

    public partial class UseCustomXamlClass : Window

    {

        [STAThread]

        public static void Main()

        {

            Application app = new Application();

            app.Run(new UseCustomXamlClass());

        }

        public UseCustomXamlClass()

        {

            InitializeComponent();

            for (int i = 0; i < 5; i++)

            {

                CenteredButton btn = new CenteredButton();

                btn.Content = "Button No. " + (i + 1);

                stack.Children.Add(btn);

            }

        }

    }

}

除了具有Window的XAML,以及可能也有一些element和控件的XAML之外,工程中常常也会有一个Application对象的XAML。这么做的话,你的程序就不再需要显式地定义Main方法。

让我们试试看。此工程名为IncludeApplicationDefinition,它具有两个XAML文件(一个定义Application,一个定义Window)以及两个对应的code-behind文件。Window的XAML文件只包含一个按钮,所以很短:

MyWindow.xaml

<!-- ===========================================

     MyWindow.xaml (c) 2006 by Charles Petzold

     =========================================== -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        x:Class="Petzold.IncludeApplicationDefinition.MyWindow"

        Title="Include Application Definition"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

    <Button HorizontalAlignment="Center"

            VerticalAlignment="Center"

            Margin="1.5in"

            Click="ButtonOnClick">

        Click the Button

    </Button>

</Window>

此XAML的命名空间是Petzold.IncludeApplicationDefinition,和MyWindow类是相同的命名空间。MyWindow继承自Window,一部分(partial)定义在MyWindow.cs中。此类的构造函数调用InitializeComponent,并且也包含Button的Click事件处理函数。

MyWindow.cs

//-----------------------------------------

// MyWindow.cs (c) 2006 by Charles Petzold

//-----------------------------------------

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

namespace Petzold.IncludeApplicationDefinition

{

    public partial class MyWindow : Window

    {

        public MyWindow()

        {

            InitializeComponent();

        }

        void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            Button btn = sender as Button;

            MessageBox.Show("The button labled '" + btn.Content +

                "' has been clicked.");

        }

    }

}

第二个XAML文件负责Application对象。命名空间依然是Petzold.Include ApplicationDefinition,但是类是MyApplication。

MyApplication.xaml

<!-- ================================================

     MyApplication.xaml (c) 2006 by Charles Petzold

     ================================================ -->

<Applicationxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        x:Class="Petzold.IncludeApplicationDefinition.MyApplication"

        StartupUri="MyWindow.xaml" />

MyApplication.xaml文件的Build Action必须是ApplicationDefinition。小心:在某些情况下(例如更改文件名),Visual Studio可能会改变Build Action的设定。如果你使用XAML文件来定义一个Application对象,而你得到一个错误信息告诉你没有Main方法,那么请检查此Application XAML的Build Action设定。

请注意最后的StartupUri attribute,引用到MyWindow.xaml文件。当然,在应用程序运行的时候,MyWindow.xaml文件已经被编译成MyWindow.baml文件,成为应用程序的资源,但它依然是你希望应用程序一开始显示的窗口。这里的StartupUri attribute取代了Main里面调用的Run方法。

最后,下面是MyApplication.cs,它什么事都没做。在某些应用程序中,此文件可能具有Application对象需要的事件处理函数(如果MyApplication.xaml文件中的attribute定义了事件处理函数的话)。

MyApplication.cs

//----------------------------------------------

// MyApplication.cs (c) 2006 by Charles Petzold

//----------------------------------------------

using System;

using System.Windows;

namespace Petzold.IncludeApplicationDefinition

{

    public partial class MyApplication : Application

    {

    }

}

整个工程已经全部介绍完毕。显然这里没有Main方法,但是在你编译程序之后,你可以看看产生出来的MyApplication.g.cs文件,你就可以看到Main。

MyApplication.cs文件是如此地没有意义,以至于你可以将它从工程中删除,此工程依然可以编译运行,一如往常。(事实上,当我第一次将这些文件组成工程时,我偶然地在MyApplication.xaml与MyApplication.cs中使用了不同的类名,结果依然顺利编译运行!)

只包含XAML文件,完全没有程序代码文件,这样的工程也是有可能的(虽然对许多应用程序来说并非如此)。没有程序代码的工程通常会在XAML中使用数据绑定,或使用某些XAML animation。下面的工程名为CompileXamlOnly,具有两个文件。第一个是Application文件:

XamlOnlyApp.xaml

<!-- ==============================================

    XamlOnlyApp.xaml (c) 2006 by Charles Petzold

    ============================================== -->

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                 StartupUri="XamlOnlyWindow.xaml" />

我稍早提到过,此Application文件必须将Build Action设定成ApplicationDefinition,否则一切就无法顺利运行。请注意,StartupUri是XamlOnlyWindow.xaml文件,内容如下:

XamlOnlyWindow.xaml

<!-- =================================================

    XamlOnlyWindow.xaml (c) 2006 by Charles Petzold

    ================================================= -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Title="Compile XAML Only"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

    <StackPanel>

        <Button HorizontalAlignment="Center"

                Margin="24">

            Just a Button

        </Button>

        <Ellipse Width="200"

            Height="100"

            Margin="24"

            Stroke="Red"

             StrokeThickness="10" />

        <ListBox Width="100"

                   Height="100"

                   Margin="24">

            <ListBoxItem>Sunday</ListBoxItem>

            <ListBoxItem>Monday</ListBoxItem>

            <ListBoxItem>Tuesday</ListBoxItem>

            <ListBoxItem>Wednesday</ListBoxItem>

            <ListBoxItem>Thursday</ListBoxItem>

            <ListBoxItem>Friday</ListBoxItem>

            <ListBoxItem>Saturday</ListBoxItem>

        </ListBox>

    </StackPanel>

</Window>

请注意,两个文件都没有定义类名称。如果你检查一下,你会发现Visual Studio只为Application XAML产生了文件,并将类命名为Application__。这个产生出来的文件包含了Main方法。对于Window XAML来说,没有产生文件。但是Visual Studio会将此Window编译成一个BAML文件,所以整个组织结构会类似于有明确程序代码的工程。对Application类来说,并不需要BAML文件,因为它没有定义一个element tree,或者定义运行过程中需要的任何东西。

假设你一开始有一个只有XAML的应用程序,然后你决定此应用程序需要一些C# 程序代码。但是你不想为此建立一个全新的C# 文件。我不知道你的原因是什么。(或许你会告诉我,这是你的项目,你高兴这么做。)幸好,你可以在XAML内嵌入C# 程序代码,这样做看起来不美观,但确实是可行的。

此工程名为EmbedCodeInXaml,第一个文件是Application类:

EmbeddedCodeApp.xaml

<!-- ==================================================

    EmbeddedCodeApp.xaml (c) 2006 by Charles Petzold

    ================================================== -->

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                 StartupUri="EmbeddedCodeWindow.xaml" />

此StartupUri引用到EmbeddedCodeWindow.xaml文件,此文件具有一个Button、一个Ellipse和一个ListBox,也具有一些内嵌的C# 程序代码。

EmbeddedCodeWindow.xaml

<!-- =====================================================

    EmbeddedCodeWindow.xaml (c) 2006 by Charles Petzold

    ===================================================== -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        x:Class="Petzold.CompileXamlOnly.EmbeddedCodeWindow"

        Title="Embed Code in XAML"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize"

        Loaded="WindowOnLoaded">

    <StackPanel>

        <Button HorizontalAlignment="Center"

                Margin="24"

                Click="ButtonOnClick">

            Click the Button

        </Button>

        <Ellipse Name="elips"

                Width="200"

                Height="100"

                Margin="24"

                Stroke="Red"

                StrokeThickness="10" />

        <ListBox Name="lstbox"

                Width="150"

                Height="150"

                Margin="24"

                SelectionChanged="ListBoxOnSelection" />

        <x:Code>

            <![CDATA[

        void WindowOnLoaded(object sender, RoutedEventArgs args)

        {

            foreach (System.Reflection.PropertyInfo prop in

                                            typeof(Brushes).GetProperties())

                lstbox.Items.Add(prop.Name);

        }

        void ButtonOnClick(object sender, RoutedEventArgs args)

        {

            Button btn = sender as Button;

            MessageBox.Show("The button labeled '" +

                                 btn.Content +

                                 "' has been clicked.");

        }

        void ListBoxOnSelection(object sender, SelectionChangedEventArgs args)

        {

            string strItem = lstbox.SelectedItem as string;

            System.Reflection.PropertyInfo prop =

                                           typeof(Brushes).GetProperty(strItem);

            elips.Fill = (Brush)prop.GetValue(null, null);

        }

            ]]>

        </x:Code>

    </StackPanel>

</Window>

嵌入的程序代码需要使用x:Code element以及x:Code element内的CDATA section。XML规范定义了CDATA(意思是“character data”)为XML文件内的一个section,作为“没有任何markup的文字内容”。对于C# 或其他语言,当然是属于CDATA。

CDATA section一定是以“<![CDATA[”开始,以“]]>”结束。在CDATA section内,绝对不可以出现“]]>”,因此,如果你写出下面的程序,就会有问题了:

if (array1[array2[i]]>5)

中间应该要插入一个空格,才不会被误判为CDATA的结尾。

编译此项目的时候,C# 程序代码被放进EmbeddedCodeWindow.g.cs文件。此嵌入式程序代码无法定义字段。如果产生出来的程序代码没有自动将这些命名空间用using指示符(directive)包含进来的话,此嵌入程序代码可能需要完整的命名空间。请注意,出现在EmbeddedCodeWindow.xaml的嵌入式程序代码,需要为类冠以完整的命名空间System. Reflection namespace。

虽然在XAML文件中嵌入C# 程序代码,好像很方便,但是却相当丑且不灵活。如果你不在XAML中嵌入C# 程序代码,你应该会过着更快乐、更长寿、更满足的生活。

或许你还记得第5章“Stack and Wrap”的DesignAButton程序。该程序指定一个StackPanel到Button的Content property,然后用两个Polyline对象、一个Label、一个Image(显示图BOOK06.ICO)来装饰StackPanel。让我们试着只用XAML(和此icon文件)模仿此程序。

此icon文件必须将Build Action设定成Resource。下面的DesignXamlButtonApp.xaml文件必须将Build Action设定成Application Definition。

DesignXamlButtonApp.xaml

<!-- ======================================================

    DesignXamlButtonApp.xaml (c) 2006 by Charles Petzold

    ====================================================== -->

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                 StartupUri="DesignXamlButtonWindow.xaml" />

此工程最后的一部分,是Build Action被设定成Page的Window的XAML文件。

DesignXamlButtonWindow.xaml

<!-- =========================================================

    DesignXamlButtonWindow.xaml (c) 2006 by Charles Petzold

    ========================================================= -->

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        Title="Design XAML Button"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

    <Button HorizontalAlignment="Center"

            VerticalAlignment="Center"

            Margin="24">

        <StackPanel>

            <Polyline Stroke="Black"

                    Points="0 10,10 0,20 10,30 0,40 10,50 0,

                        60 10,70 0,80 10,90 0,100 10" />

            <Image Margin="0,10,0,0"

                Source="BOOK06.ICO"

                Stretch="None" />

            <Label HorizontalAlignment="Center">

                _Read Books!

            </Label>

            <Polyline Stroke="Black"

                        Points="0 0,10 10,20 0,30 10,40 0,50 10,

                              60 0,70 10,80 0,90 10,100 0" />

        </StackPanel>

    </Button>

</Window>

此窗口只包含一个Button,但是此Button包含一个StackPanel,而此StackPanel包含其他4个element。这两个Polyline element指定一系列(11个)XY坐标点。请注意,这里使用逗号分隔这些点。你可以改用空格来分隔点,用逗号来分隔XY坐标。或者你可以使用空白或者逗号同时作为两种分隔。

此XAML Image element也相当优雅。不再定义一个Uri对象,然后从此Uri建立一个BitmapImage,然后再将BitmapImage对象指定给Image的Source property(这一切都需要写在C# 程序代码中,你可以在原始的DesignAButton.cs文件中看到这一段程序代码)。现在你可以简单地将Source property设定成icon文件的名称。如果你想要的话,你可以引用此资源的URI(“pack://application:,,/BOOK06.ICO”),但是我认为文件名看起来清楚得多。

XAML包含许多像这样的小快捷方式,下一章开始,我们会陆续探讨它们。但是首先,我们需要一个程序,可以让我们互动地(interactively)进行XAML的实验,并且研究XAML的语法到极致。