现在的位置: 首页 > 综合 > 正文

XAML(和Camel押韵) – 《Windows Presentation Foundation 程序设计指南》 – 免费试读 – book.csdn.net

2014年02月12日 ⁄ 综合 ⁄ 共 24637字 ⁄ 字号 评论关闭

导读:

本文转自
http://book.csdn.net/bookfiles/591/10059119378.shtml

 

 

第19章  XAML(和Camel押韵)

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

    Hello, XAML!

这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相互辉映:

   

        Hello, XAML!

   

   

   

        http://www.charlespetzold.com/PetzoldTattoo.jpg"

                 Stretch="None" />

   

在此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文件:

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

    Hello, XAML!

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

XamlButton.xaml

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

    Hello, XAML!

这里使用灰色的背景,表示此文件是本书源代码的一部分,你可以从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

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

   

        Hello, XAML!

   

   

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

   

        Sunday

        Monday

        Tuesday

        Wednesday

        Thursday

        Friday

        Saturday

   

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

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

    WindowTitle="Xaml Page">

   

       

            Hello, XAML!

       

       

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

       

            Sunday

            Monday

            Tuesday

            Wednesday

            Thursday

            Friday

            Saturday

       

   

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

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

string strXaml =

    "

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

    "     Click me!" +

    "";

为了让字符串更具有可读性,我使用单引号而非双引号来表示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 =

                "";

            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

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

   

   

        Height="100"

        Margin="24"

        Stroke="Red"

        StrokeThickness="10" />

   

            Height="100"

            Margin="24">

        Sunday

        Monday

        Tuesday

        Wednesday

        Thursday

        Friday

        Saturday

   

如你所见,此文件非常类似于独立的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

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

        Title="Load Xaml Window"

        SizeToContent="WidthAndHeight"

        ResizeMode="CanMinimize">

   

       

       

                   Height="100"

                   Margin="24"

                   Stroke="Red"

                   StrokeThickness="10" />

       

                   Height="100"

                   Margin="24">

            Sunday

            Monday

            Tuesday

            Wednesday

            Thursday

             Friday

            Saturday

       

   

此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文件的整体结构可能是:

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

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

          x:Class="MyNamespace.MyClassName"

          ... >

    ...

这里的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

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">

   

       

       

            Width="200"

            Height="100"

            Margin="24"

            Stroke="Black"/>

       

            Width="150"

            Height="150"

            Margin="24"

            SelectionChanged="ListBoxOnSelection" />

   

实际上,你将会看到,此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”作为一个一般目的的解决方案。)习惯上使用简短的前缀,但这不是硬性规定。如果你的项目包含来自多个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

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">

   

       

       

                               VerticalAlignment="Center"

                               Margin="24"

                               SelectionChanged="ColorGridBoxOnSelectionChanged" />

       

   

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]

       

抱歉!评论已关闭.