目录:点击这里
上一篇:【翻译】Pro.Silverlight.5.in.CSharp.4th.Edition - 第一章 Silverlight介绍 03
第二章 XAML
XAML(读音zammel,咋没儿~),全称Extensible Application Markup Language(扩展应用标记语言),是一种用于实例化.NET对象的标记语言。尽管XAML这种技术可以应用于各种不同的领域,不过在最初设计的时候,它是WPF的一部分(WPF是Windows Presentation Foundation的简称,可以让开发者创建丰富的用户界面)。在Silverlight应用程序中使用和WPF相同的标准来创建用户界面。
从概念上讲,XAML所扮演的角色与HTML相似,或者说更接近XHTML(更严谨更纯净的HTML版本)。你可以使用XHTML来定义元素并构建出一个普通的web页面;与之类似,你可以用XAML来定义元素并构建出一个XAML内容区域。你可以通过客户端的JavaScript脚本来操作XHTML元素;类似地,你可以通过编写客户端的C#代码来操作XAML元素。另外,XAML和XHTML有许多相同的语法习惯。和XHTML类似,XAML也是一种基于XML的标记语言,你可以根据自己的需要在XML语法前提下对元素进行分布排列和嵌套。
通过本章你会对XAML有一个详细的了解并且尝试做一个简单的单页应用程序。一旦你了解了XAML的大致规则,你就会知道在Silverlight中哪些是可实现的、哪些是不可实现的——另外还会了解到如何手动修改XAML内容。通过浏览Silverlight XAML文档中的标签,你还会对支撑Silverlight用户界面的对象模型有更细致的了解,而且为接下来的深入研究打下基础。
在本章的最后,你会了解到两种基于Silverlight的特性的用来扩展XAML的标记扩展。第一个是XAML资源和静态资源(StaticResource)扩展,可以用于简化清爽你的代码和实现标记的重用;第二个是绑定扩展,可以用于将两个元素联系在一起。这两种技术会在本书中随处可见,是Silverlight开发过程中需要掌握的一部分核心内容。
XAML 基础
掌握如下几个规则后,你会发现其实XAML标准比较简单:
- XAML文档中的每一个元素都映射为Silverlight类中的一个实例。类的名称和这个元素的名称完全一致。比如说,元素<Button>会引导Silverlight创建一个Button对象。
- 和XML文档一样,可以在元素中嵌套另一元素。XAML让每个类自己可以灵活处理这种情况。嵌套通常是一种用来标识元素之间包含和被包含关系的方式——换句话说,如果在XAML中有一个Button元素被放置于一个Grid元素中,那么对应在用户界面中则会展现为一个表格中有一个按钮。
- XAML中通过attributes(特性)能设置对应代码中每个类的properties(属性)。不过,在某些情况下,attribute无法胜任这项工作。这种情况下会用到特定语法的嵌套标签。
提示:如果你不了解XML,那么我建议你在学习XAML之前对XML做一些基本的了解,可以参考网上教程www.w3schools.com/xml。
在继续学习之前,请看看下面这个由VS自动生成的纯粹骨架子式的XAML文档,这个在页面中展现为一个空白页面。为了方便说明,每一行都进行了编号。
1 <UserControl x:Class="SilverlightApplication1.MainPage" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"> 7
8 <Grid x:Name="LayoutRoot"> 9 </Grid> 10 </UserControl>
这个文档只包含了两个元素——最外一层是一个UserControl,这个元素在页面中包括了其它所有的Silverlight内容;另一个是Grid,在这个Grid中你可以放置你所用到的所有元素。
和XML文档类似,最外层的元素只能有一个——以上面那个XAML例子来说,一旦使用</UserControl>将UserControl标签关闭后,这个文档也就到了尾部,后面不能够继续增加其它内容了。
XAML 命名空间
当你在XAML文件中使用了<UserControl>这样的元素的时候,Silverlight解析器会识别为创建一个UserControl类的实例。不过,他并不确定是要生成具体哪种UserControl类的实例(因为类名冲突太常见了)。毕竟,就算Silverlight命名空间中叫做UserControl这个名字的类只有一种,但是并不能保证你自己不会创建同样名字的自定义的实体类。鉴于此,很显然需要给XAML解析器指明命名空间的信息以保证用到的是正确的元素。
在Silverlight中,类的定义是通过XML命名空间和Silverlight命名空间的映射关系来实现的。在先前的那个例子中,有四个命名空间的定义如下:
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
在XML的概念中,xmlns是一个比较特殊的属性,它是为了实现命名空间的声明。这段标记所声明了四个名空间(每一个由Blend创建的页面都包含这四个命名空间)
备注:XML命名空间是通过attributes来完成声明的。这些声明可以放置在任意元素的起始标签中。不过,按照惯例通常是在最开始的标签中声明文档中所需要用到的所有命名空间。一旦命名空间做了声明,便可以在文档的任意位置使用。
核心Silverlight命名空间
前面示例中前两个命名空间是最重要的,他们用于访问Silverlight运行时的核心部分:
- http://schemas.microsoft.com/winfx/2006/xaml/presentation:是核心Silverlight命名空间。它包含了所有基本的Silverlight类库,比如UserControl和Grid。一般来说,这个命名空间的声明不带前缀,因此它是整个文档的默认命名空间。换句话说,只要你不做特定标记,那么元素就自动认定为这个命名空间下的。
- http://schemas.microsoft.com/winfx/2006/xaml:是XAML命名空间。它所包含的各种XAML特性的实用性很强,利用这些特性修改XAML细节可以增强页面的可读性。这个命名空间映射为前缀x。这表示你可以在元素或者attribute的前面使用x前缀来标识这个元素或者attribute属于这个命名空间(比如<x:ElementName> 和 x:Class="ClassName")
命名空间信息引导XAML解析器找到准确的类。比如说,在解析到UserControl或者Grid元素的时候,解析器发现他们使用的是默认命名空间http://schemas.microsoft.com/winfx/2006/xaml/presentation,那么它会搜索相应的Silverlight命名空间,直到发现与之相匹配的类:System.Windows.UserControl和System.Windows.Controls.Grid。
XML命名空间和Silverlight命名空间
XML命名空间和Silverlight命名空间不是一一对应的关系。相反,所有的Silverlight命名空间共享同一个XML命名空间。XAML的创建者最初这样设计有多种原因。按照惯例,XML命名空间通常是URI(比如上面的例子就可以看出来)。这些URI看起来像是网络中的某个地址,其实不是。选择这种格式的目的是为了防止和其它组织无意中创建的基于XML的语言有相同的命名空间——因为“schemas.microsoft.com”这个域名是属于微软的,意思就是说,只有微软会在XML命名空间的名字上使用这样的名称。
不采用一一对应的处理方式还有一个原因:如果一一对应的话,会让你的XAML文档的结构乱七八糟成“翔”状。如果每一个Silverlight命名空间都有一个自己专属的XML命名空间,那么对你用到的每一个控件,你都得标记上正确的命名空间,这会马上让你本人和你的XAML文档一起凌乱。相反,Silverlight的创建者选择将所有的包含用户界面的元素的Silverlight命名空间映射到一个单一的XML命名空间。因为在不同的Silverlight命名空间下,不同的类对应的名称也不一样。
除了上面那些核心命名空间,还有许多功能针对性强的非必须的命名空间。
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006:XAML兼容性命名空间。用这个命名空间可以告知XAML解析器哪些信息是必须处理的,哪些信息是可以忽略的。
- http://schemas.microsoft.com/expression/blend/2008:这个命名空间是为在Blend(以及当前的VS 2010)中提供的特殊设计的XAML特性而保留下来的。现在主要用于设置页面在设计模式下的页面大小。
这两个命名空间在如下的一行中使用:
6 mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
此行中所示的DesignWidth和DesignHeight这两个属性属于http://schemas.microsoft.com/expression/blend/2008这个命名空间。这两项设置告诉设计工具在设计时提供一个600×480像素大小的页面。如果没有这样的设置,你在设计时看到的页面可能是一个压扁的效果,这无法让你预览到界面真实的样子,或者你不得不用硬编码的方式设置Width和Height这两个属性(这种做法很不妥,因为这会导致你在系统在运行时页面尺寸是一个固定值,无法去适应浏览器窗口的大小)
备注:在本书中的例子中很少出现这些命名空间,因为它们相对来说不是特别的关键。这些命名空间是给设计工具和XAML阅读器用的,Silverlight运行时使用不到。
自定义命名空间
有些情况下,你需要在XAML文件中用到你自己的命名空间——最常见的例子就是当你要在XAML页面中使用你自己创建(或者第三方)的Silverlight控件。这种情况下,你需要定义一个新的XML命名空间前缀并将其映射到你的程序集上。语法如下:
<UserControl x:Class="SilverlightApplication1.MainPage" xmlns:w="clr-namespace:Widgets;assembly=WidgetLibrary" ... >
这条命名空间的声明中包含了三个信息:
- XML命名空间前缀:在XAML页面中使用这个前缀来代表对应的命名空间。比如在这个例子中前缀是w。(只要不和其他命名空间的前缀冲突,可以随意命名)
- .NET命名空间:本例中,相应的类处于Widgets命名空间中。如果你的类需要用于多种命名空间中,你可以将它们映射到不用的XML命名空间,也可以映射到同一个XML命名空间(只要命名不冲突即可)。
- 程序集:本例中,类属于组件WidgetLibrary.dll中的一部分(命名时去掉“.dll”这个扩展名)。Silverlight会自动在项目编译后对应的XAP包中寻找这个程序集。
备注:记住,Silverlight所使用的公共语言运行时(CLR)是一个定制精简后的版本。因此,Silverlight应用程序不能使用完整的.NET类库程序集。反之,他需要使用Silverlight类库。在VS中创建Silverlight类库很容易:创建项目的时候选择Silverlight Class Library这一项即可。
如果你使用的控件本身就属于项目的一部分,那么就可以忽略掉命名空间映射中的程序集部分,比如这样处理:
xmlns:w="clr-namespace:Widgets"
一旦将.NET命名空间映射到XML命名空间后,你就可以再XAML文档中任意位置使用这个前缀。比如说,本例中如果Widgets命名空间中包含一个名为HotButton的控件,那么在XAML中可以按照如下的写法来创建一个HotButton类型的实例:
<w:HotButton Content="Click Me!" Click="DoSomething"></w:HotButton>
本书中随处可以看到使用这种手段来访问Silverlight扩展组件和Silverlight Toolkit。
Code-Behind类
XAML用于创建用户界面,不过考虑到应用的功能性方面,必须有一种方式将包含应用程序代码事件句柄串联在一起。用Class这个属性可以很方便的完成这个工作:
<UserControl x:Class="SilverlightApplication1.MainPage"
置于Class之前的前缀x表明这属于XAML语言的一个很常用的玩意,而不是什么特殊的Silverlight组件。
事实上,这个Class属性会告诉Silverlight解析器去生成一个命名很特殊的新类。这个类继承于此XML元素的同名类。换句话说,这个例子创建了一个命名为“SilverlightApplication1.MainPage”(原书中是SilverlightProject1.MainPage,我觉得可能是笔误)的新类,继承于UserControl。这个类自动生成的那部分和你在对应的Code-behind文件中的所编写的代码融合在一起。
通常来说,每一个XAML文件都有一个与之对应的客户端C#编写的code-behind类,VS给MainPage.xaml文件创建了一个名为“MainPage.xaml.cs”的code-beihind类。其中的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace SilverlightApplication1 { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } } }
VS自动生成的MainPage.xaml.cs中不存在任何实质功能性的代码。不过,它包含了一个非常重要的细节——默认构造函数,在创建了对应类的一个实例的时候会调用InitializeComponent()这个方法。这个方法会将XAML进行解析,生成所有对应的对象、按照XAML文件中的内容正确地设置好相应的属性并且将其中定义的所有事件句柄完成注册操作。
备注:InitializeComponent()这个方法非常关键,因此,绝对不可以将构造函数中调用的这句话删掉;另外,当你给页面增加另外一个构造函数的时候,也务必保证这个构造函数调用了InitializeComponent()。
元素的命名
在code-behind类中进行编写代码的时候,经常会要对XAML中的元素进行操作。举个栗子,你可能要进行属性的读写、或者注册/注销事件句柄。要实现这样的操作,控件则必须包含一个名称属性。在之前的例子中,Grid控件已经包含了Name属性,因此我们便可以子啊对应的code-behind代码文件中对这个控件进行操作。
8 <Grid x:Name="LayoutRoot"> 9 </Grid>
Name属性会告诉XAML解析器在MainPage类中的代码自动生成那部分增加一个类似下面这样的字段。
private System.Windows.Controls.Grid LayoutRoot;
这样你就可以在代码页中使用LayoutRoot这个名称来编写和这个Grid一些交互性的代码了。
提示:在传统的Windows窗体程序中,每一个控件都有一个自己的名称。而在Silverlight应用程序中则没有这样的强制要求。如果你在代码中没有和这个元素有任何交互,那么你完全可以将XAML中相关标记的Name属性去掉。本书中的示例为了标记显得简明一些,将那些代码中用不到的元素的名称都忽略了。
XAML中的属性和事件
到目前为止,你所看到的示例确实有点单调乏味——一个空白页面上寄宿着一个空的Grid控件。在进一步之前,我认为有必要展示一个包含了多一些的元素并且显得稍微绚丽一点的页面。如图2-1展示了一个自问自答的示例。
图2-1 点击“Ask the eight ball”按钮显示出答案。
此页面中包含四个元素:一个网格Grid(Silverlight中最常用的布局工具),两个文本输入框TextBox和一个按钮Button。这个示例中排列和配置元素所用到的标记内容可比之前的示例要多得多。为了快速的了解整体结构,我们列出了如下一个简要的标记清单(其中一些暂时不作为重点考虑的细节用省略号代替):
<UserControl x:Class="EightBall.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="grid1"> <Grid.Background> ... </Grid.Background> <Grid.RowDefinitions> ... </Grid.RowDefinitions> <TextBox x:Name="txtQuestion" ... > </TextBox> <Button x:Name="cmdAnswer" ... > </Button> <TextBox x:Name="txtAnswer" ... > </TextBox> </Grid> </UserControl>
接下来的章节会详细介绍这段文档的各部分细节——通过这个实际例子来学习XAML的语法规则。
简单属性和类型转换器
正如你所看到的,XML元素的attributes值设置了相应Silverlight对象的properties(此处不纠结于attribute和property这两者的具体中文含义了,就用这两个英文单词可能更清晰)。比如,在eight ball这个示例中的文本输入框设置了alignment、margin和font这几个属性:
<TextBox x:Name="txtQuestion" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" FontFamily="Verdana" FontSize="24" Foreground="Green" ... >
要让这些设置起作用,System.Windows.Controls.TextBox这个类必须提供了如下属性:VerticalAlignment,、HorizontalAlignment、FontFamily、FontSize和Foreground。在接下来的几章中你会逐渐了解这些属性的具体含义。
提示:有些特殊的字符不能直接输入到属性字符串中,比如引号、与符号(&)、尖括号(<>)。如果要使用这个符号,则必须用等价的XML字符串来替换:引号是" ,与符号是&,<(小于)符号是< (less than的缩写),>(大于)符号是>(greater than的缩写)。当然这样处理仅限于XML文档中;在代码中设置属性的时候不存在这样的问题。
XAML解析器需要做大量的工作来让属性机制能正常运作(这个工作量比你一开始所预计的要多很多)。XML属性值通常是个简单的字符串文本,而Silverlight对象的属性则可以是任意的.NET类型。在前面那个示例中,有两个属性(VerticalAlignment 和 HorizontalAlignment)用到的是枚举,一个属性(FontFamily)用的是字符串,还有一个(Foreground)用的是Brush类型。
XAML解析器通过执行一种格式转换来将这种字符串值和非字符串的属性之间的差别消除掉。这种转换的过程是由类型转换器(type converters)来实现的。这是从完整的.NET框架中搬过来的一个基本组件。
从本质上来说,每一个类型转换器都扮演一种角色——提供特定的.NET数据类型和另一个.NET数据类型之间的相互转换的方法,比如本例中的数据类型是字符串。XAML解析器使用如下两个步骤来寻找相应的类型转换器:-
- 检查属性的声明,寻找TypeConverter特性。(如果存在的话,那这个属性的值会指明用来完成转换的是哪个类。)举个栗子,当你使用Foreground这样的属性的时候,.NET会检查Foreground属性的声明。
- 如果属性的声明中没有TypeConverter特性,那么XAML解析器会检查相应的数据类型的类的声明。比如,Foreground属性用的是一个Brush对象。Brush类(和它的派生子类)使用的是BrushConverter,因为Brush类应用了特性TypeConverter(typeof(BrushConverter))。
如果在属性声明和类声明中都没有应用相关的类型转换器特性,XAML解析器就会生成一个错误异常。
这种机制虽然简单,但是非常灵活。如果你是在类上设置了类型转换器特性,那么所有的数据类型是这个类的属性都应用了相应的转换器。另一方面,如果你只是要单独将某一个特定的属性实现类型转换,那么只需要在这个属性的声明应用TypeConverter特性即可。
从技术的角度来说,可以用编码的方式使用类型转换器,但是这个语法相对来说有点复杂。因此一般都是直接设置属性——这样不仅快,而且也避免了拼写错误的可能性(这种错误不会在编译时报错,只有在运行时才能捕获到)。这个问题不会影响到XAML,因为在编译时环境下就会对XAML进行解析和验证。
备注:XAML和所有基于XML的语言一样,是区分大小写的。这就意味着用<button>来代替<Button>。不过类型转换器一般不区分大小写,这就意味着Foreground="White"和Foreground="white"的效果一样。
某些类定义了内容属性(ContentProperty),这个可以用来将开始和结束标签之前的字符串内容作为那个特定属性的值。比如,Button类将Content指定为内容属性,这就意味下面这两个标记是完全相同的:
<Button>Click Me!</Button>
<Button Content="Click Me!"></Button>
第一种写法更为简洁,但是我们更多看到的是第二种写法:不仅仅是因为它是VS的属性窗口中配置元素的标准写法,还有一个原因是在Silverlight 4 版本之前的各版Silverlight中的许多控件不支持第一种写法,只能使用第二种写法。