原文 Windows Phone 8初学者开发—第4部分:XAML简介
原文地址:
系列地址:http://channel9.msdn.com/Series/Windows-Phone-8-Development-for-Absolute-Beginners
源代码: http://aka.ms/absbeginnerdevwp8
PDF版本: http://aka.ms/absbeginnerdevwp8pdf
在本课中,我将讨论在第一课中编写的SoudBoard应用程序的XAML语法。希望您注意到了我们编写的XAML是如何影响手机预览窗格中我们见到的内容。通过查看XAML来理解绝对基本的XAML是相对简单的,但是我想指出一些并非一眼就能看出的特性和功能。
概要地讲本课的计划是:
- 我们将通过与C#的比较来讨论XAML的目的和性质
- 我们将讨论XAML的特殊特性,语言的一些隐藏功能,这些功能并非看一眼就显而易见。
我的目标是:当本课结束时您将具备足够的理解我们在本系列其余部分编写的XAML的知识,并且在我试图解释前就能够基本猜出它的作用。
1. XAML是什么?
在上一课我提到XAML的外观与HTML类似。这并非偶然。XAML其实就是XML(可扩展标记语言the eXtensible Markup Language)。
我待会儿会解释它们的关系,但是概要地讲,XML和HTML非常相似,它们具有共同的祖先。HTML用于构建web页面文档,而XML则更通用。此处“通
用”的意思是您可以针对设想的任意用途来使用它,并且您可以定义元素和特性(attributes)的名称来满足您的需要。过去开发者利用XML来存储应
用设置,在两个不同的系统之间传输数据。为了使用XML,您需要定义架构(schema),它对元素和元素特性的名称进行声明。架构类似于一个合约
(contract)。每个人,不管他是XML的生产者还是XML的使用者都遵守合约的规定以在相互间进行通讯。因此架构是XML的重要组成部分。请记住
上述分析,待会儿我们还会对他进行讨论。
XAML是XML的一种特殊用法。很显然,我们已经看到XAML与定义手机的用户界面有关。所以在这点上感觉它非常像HTML。但是有一个很大的区
别,XAML实际上用于创建类的实例和设置属性(properties)的值。例如,在前面的课程中我们在XAML中定义了一个按钮:
上述代码大致等同于以下的C#代码:
我已将上述C#代码添加到MainPage类的构造函数中。我待会儿会讨论MainPage.xaml和MainPage.xaml.cs之间的关
系,但是我们已经看到如何在MainPage.xaml.cs中通过编写程序方式的C#代码来定义行为。这里我仅仅编写了在MainPage类的新实例被
创建时执行的代码(通过在类的构造函数中编写代码来实现)。
现在我有了两个按钮,一个以声明方式在XAML中定义、含有“内容Hello World”并且在单击按钮时会发出嘎嘎的叫声,另一个是新创建的含有内容“Quack” 的按钮。当我们运行应用:
我们仅能看见一个按钮。那是因为我刚在C#中以程序方式(在MainPage类的构造函数中)创建的按钮位于我们在上一课中在XAML中创建的按钮之上。我将加入一行C#代码用于设置新的Quack按钮的边距(margin),将它移动到靠左210像素的位置。
按钮的边距(Margin)属性是Thickness类型,它是一个通用用途的代表4个维度的类。在这里我们创建了一个新的Thickness类并设置它的构造函数的第一个参数为210像素。当我们再次运行应用:
我们现在可以看到两个按钮。
更大的问题是我们有了两个(几乎)相同的按钮,一个以声明方式在XAML中创建,另一个以程序方式在C#中创建。
当我这样创建一个新的XAML元素时:
<Button></Button>
我基本上是在创建了一个新的按钮类的实例。
当我设置按钮元素的特性时,我基本上在设置按钮类实例的属性。
重要的是:XAML是创建类的实例和设置对象属性的一种更加简化和简洁的语法。用10行C#代码才能完成的工作只需要一行XAML。(即使在编辑器中我将它分成数行,它仍然比用C#创建对象的方法要简短得多)
此外,使用XAML我可以在手机预览窗格中迅速获得反馈。我立即可以看到更改的影响。在编写程序方式的C#代码时,当需要观察对代码的改动如何工作时我需要运行应用程序。
2. 类型转换器简介
如果您有敏锐的眼光,当遇到HorizontalAlignment特性时,您可能已经注意到XAML和C#版本的差异。如果您尝试:
myButton.HorizontalAlignment = “Left”;
将会产生一个编译错误。XAML分析器将通过值转换器来执行字符串“Left”到枚举值
System.Windows.HorizontalAlignment.Left的转换。类型转换器是一个类,它可以将字符串值转换为强类
型,Windows 8
API中有若干类型转换器,我们会在这个系列中一直使用它们。在本例中的HorizontalAlignment属性,当被微软的开发人员开发时,在源代
码中被一个特殊的特性标记,它指示XAML分析器通过类型转换器方法尝试将字符串“Left”匹配为枚举值
System.Windows.HorizontalAlignment.Left。
当您试图拼错“Left”时,看看会发生什么:
您将会收到一个编译错误,因为类型转换器不能找到一个准确的匹配以转换为枚举值System.Windows.HorizontalAlignment.Left。
因此,XAML的第一个特点是:它是一种简洁的创建类的实例的手段。在构建Windows 8应用时,它被用来创建用户界面元素的实例,但是XAML不仅仅是一项用户界面技术,它可以在其他技术中用于其他目的。
3. 理解XMAL命名空间声明
接下来,让我们讨论MainPage.xaml文件顶部的代码,我们在之前一直没有涉及到它。
在文件顶部我们看到以下内容:
当您在查看上述内容时,回忆一下我刚才说的—有关架构属于XML的一部分。如果确实如此,那么XAML在哪里保证遵守架构?
观察第3行至第8行。MainPage.xaml需要保证遵守六个架构。每一个都通过xmlns特性来定义。在第3行定义的第一个xmlns是缺省的命名空间,换句话说没有冒号和冒号后的单词,就像你在第4行至第8行看到的那样。
第4行至第8行的其余命名空间使用名称/冒号的组合。因此,需要说明的是:x 或:phone是与架构(我们将其称为合约)相关的命名空间。MainPage.xaml其余部分的每个元素或特性必须遵守至少这些架构中的一个,否则文档就是无效的。换句话说,如果XAML中的元素或特性没有在这些命名空间中定义,那么就不能保证编译器(即分析源代码并创建可执行文件的程序)在手机上运行,编译器将无法理解如何执行特定的指令。
因此,在本例中:
<Grid x:Name="LayoutRoot" Background="Transparent">
我们将期望Grid元素和Background特性是缺省架构的一部分,缺省架构对应第3行定义的缺省命名空间。
但是,x:Name是对应第4行定义的x:命名空间的架构的一部分。
我有一个好主意,让我们首先定位到缺省的命名空间以了解更多有关命名空间的构成:
http://schemas.microsoft.com/winfx/2006/xaml/presentation
什么?!架构在该URL实际上并不存在!在这个意义上讲,架构并没有在该URL发布并可以查看。相反,架构只是一个唯一的名称,类似于我们在C#中 使用命名空间识别两个具有相同名称的类。架构(因此,在XAML中的命名空间)使得类名称可以被排序,有点类似于姓氏和名字。该URL,或更恰当地说我们 应该认为它是URI(Uniform Resource IDENTIFIER 统一资源标示符,而不是Locator 定位器),被用作命名空间的标示符。XML命名空间是对各类应用程序分析XAML的指令。Windows运行时XAML分析器将努力把它变成可执行代码,而Visual Studio和Blend设计器将努力把它变成设计时的体验。
因此,第二个XML命名空间定义了一个映射,x:属于该架构。
http://schemas.microsoft.com/winfx/2006/xaml
因此,任何前面带有x:前缀的元素或特性名称意味着他们遵守第二个架构。
但是区别在哪里?差别很小,但是第二个架构定义了XAML的内在规则。第一个架构定义了Windows 8对XAML特定使用方面的合约/规则。换句话说,我们可以不需要前缀而使用Grid, Button, MediaElement 和其他Windows Phone 8 XAML元素的事实意味着他们定义于缺省的命名空间中。
第5和第6行为Phone(手机)和Shell(界面)定义了命名空间和架构,他们位于不同的URI,注意Microsoft.Phone CLR(是Common Language Runtime的缩写,即通用语言运行时)命名空间提供的线索,定义他们的程序集在安装Windows Phone 8 API后被安装在我们的计算机上。
您可以看到,第一行:
<phone:PhoneApplicationPage
表明PhoneApplicationPage类本身就是定义的一部分。PhoneApplicationPage从 Windows.System.Controls.Page派生,它恰好也是Windows Presentation Foundation页面类和Windows应用商店应用程序页面类的父类。它含有很多被所有三种项目类型共享的基本功能。所以好处是您可以利用在本系列 中学到的内容创建WPF桌面应用和Windows应用商店应用。当然会有一些差异,但是也有很多共同点。
第7行和第8行定义用于允许Visual Studio左侧的手机预览窗格正常显示的命名空间和架构。这些指令在运行时将被忽略,这也是第9行需要完成的工作—当编译XAML代码时,忽略任何为:d的前缀。
好,我知道还有一些问题并未给出答案。我们可以花更多时间讨论细节,但是重要的是在每个添加到手机项目中的XAML文件顶部的代码是有目的的,它定 义了XAML代码必须遵守的规则。您几乎不需要修改这些代码,但是如果您将它们删除,您可能就会中断您的应用程序。所以我觉得除非有充足的理由,否则不要 改动它。在第10行至14行还有一些额外的内容,我们将在本系列的以后课程中讨论。
4. 理解.xaml和xaml.cs文件之间的关系
在Visual Studio的解决方案设计器中,您将看到XAML文件有一个箭头,它意味着我们可以展开它们以显示具有相同名称的C#文件,唯一的区别是它在结尾处包含 一个.cs的文件扩展名。如果查看.cs版本的文件,您将发现它定义了一个MainPage类,此外它定义了一个分部类(partial class):
另一半在MainPage.xaml的第1和第2行中定义:
<phone:PhoneApplicationPage
x:Class="SoundBoard.MainPage" ...
虽然他没有使用Partial这一术语(不同于程序方式的对应部分),但他确实表明了二者之间的关系。
为什么重要?这种关系意味着编译器将把MainPage.xaml 和MainPage.xaml.cs的输出合并到单个类中。 这意味着他们是整体的两个组成部分。这是一个重要的概念,XAML被编译为中间语言就像C#被编译为中间语言一样并且他们都是单个类的部分实现。可以这么 说,这使您可以在一个文件中创建类的实例,然后在另一个文件中使用它。这就是允许我在XAML中创建一个名称为_audioPlayer 的MediaElement类的实例并且在C#中调用它的方法和属性的原因。稍后我们将在本课中看到更多这样的例子。
5. 理解缺省属性
因为XAML本质上是XML文档,我们可以在其他元素内嵌入元素。我们已经看过这种情况的一个例子。
<PhoneApplicationPage>
<Grid ...>
<Grid ... >
<MediaElement ... />
<Button ... />
</Grid>
</Grid>
</PhoneApplicationPage>
或者,也许在XAML术语中更准确的说法是用户控件(UserControl)的Content属性被设置为Grid,
这里PhoneApplicationPage“包含”一个Grid,并且Grid“包含”一个MediaElement和一个Button。
并且Grid的子集合(Children collection)包括MediaElement 和Button。根据您正在使用的控件
类型,缺省属性可以使用嵌入样式的语法被填充。所以您可以这样做:
<Button Content="Hello World" ... />
或者这样
<Button ... >
Hello World
</Button>
因为Content属性是Button类的缺省属性。
6. 理解复杂属性和属性元素语法
在某些情况下,仅仅设置特性值隐藏了后台如何运作的复杂性。一个很好的例子就是设置Background=”Red”。在C#中我们已经见过这个程序,它用于完成相同的事情:
myButton.Background = newSolidColorBrush(Colors.Red);
我们需要创建一个新的SolidColorBrush实例并传递一个枚举颜色值。这是另一个很好的有关属性类型转换器的例子,我们在较早前已经学习过了。但是有些特性太复杂以至于无法表示为特性。
当属性(property)不能够简单地表示为XAML特性(attribute)时,它被称为“复杂属性”。为了演示它,首先我将从按钮中删除 Background="Red"特性,删除缺省属性"Hello World!",并用特性Content="Quack"重新添加 :
接着,在属性窗格我将Background属性设置为线性渐变画笔。
- 选择画笔(Brush)属性以显示画笔编辑器。
- 确认选择了背景(Background)。
- 选择渐变画笔标签(中间的标签)。
- 移动选择器至颜色编辑器的右上角。
我将在手机预览窗格看到以下内容:
但是更重要的是:让我们查看画笔编辑器生成的XAML。
用于创建背景的XAML不能简单地像以前使用单词”Red”那样用一个简单的字符串进行设置。相反,请注意Backgound属性被转变为按钮的自身元素:
<Button ... >
<Button.Background>
...
</Button.Background>
</Button>
这被称为“属性元素”语法,并且形式为<Control.Property>。
一个很好的例子是线性渐变画笔(LinearGradientBrush)。术语“画笔 (Brush)”意味着我们正在处理一个表示单个颜色(或多个颜色)的对象。将“画笔”理解为颜料刷,这个特殊的颜料刷将创建一个线性的渐变,颜色将从上 到下或从左到右进行改变。当然,您实际上不会去做此处代码所做的工作,因为它与Windows Phone 8应用程序的审美标准相悖。但是假设现在我们需要通过使用渐变颜色作为按钮的背景颜色来表达我们的个性。
您可以看到(如下图),如果我们需要定义一个线性渐变画笔,我们需要提供很多信息以呈现正确的画笔。例如什么时候应该进入下一个颜色。线性渐变画笔包含一组GradientStop对象,它们定义了渐变中的颜色和颜色的位置。
然而,在上面的代码段中代表LinearGradientBrush的XAML实际由Visual Studio自动进行了缩短。这是未缩短的格式:
<Button.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Red" Offset="1" />
<GradientStop Color="Black" Offset="0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
注意<LinearGradientBrush.GradientStops> 和<GradientStopCollection>元素是如何被省略的。这样做是为了简洁和紧凑,并且可以被智能化的分析器识别。首先 GradientStops属性是LinearGradientBrush的缺省属性。其次GradientStops是 GradientStopCollection类型并实现了IList<T>,此处T是GradientStop类型。有鉴于此,XAML分 析器可以推断唯一可以被嵌套在<LinearGradientBrush ... />中的是一个或多个GradientBrush的实例,每一个被隐式地添加到GradientStopCollection中。
因此故事的寓意是XAML允许我们以声明方式创建类的实例,并且在设计用户界面元素时可以进行精细的控制。即便如此,XAML分析器是智能化的,只要有足够的信息正确地创建对象图(object graph)时,它就不需要我们包含冗余的代码。
回顾
综上所述,我们已经学习了XAML的语法。大部分XAML非常简单,但也有一些并非显而易见的内容:
- XAML是XML的一个实现,并在很大程度上依赖架构和命名空间以遵守“合约”的要求,这样不同应用程序就可以创建、解释、显示或编译XAML代码。
- XAML的目的是用紧凑、简洁的语法创建类的实例并设置它们的属性。我们对在C#中通过程序方式创建按钮和在XAML中以声明方式创建按钮进行了比较,并且看到后者需要的代码更少。
- 由于它的内置功能(比如类型转换器,它允许将简单的字符串转换成类的实例),XAML需要的代码更少。
- 对于更复杂的属性设置,XAML提供属性元素语法,它利用智能的XAML分析器并依赖默认属性进行推断以减少用于表达设计的XAML代码的数量。
- 我们了解了嵌入式语法风格和嵌入式元素的性质,它们表明了视觉元素之间的关系。例如PhoneApplicationPage包含一个用于布局的Grid,它又包含输入或其他类型的控件。它们用表示包含或所属关系的开始和结束元素来表示。
- 我们了解了缺省属性,每个控件含有一个缺省的属性,可以使用与嵌入式语法相同的样式对缺省属性进行设置。
接下来,让我们学习更多有关网格的布局的内容,我们还将在下一课学习XAML的附加属性语法和如何连接事件。