剖析最简单的XMAL代码:
<Window x:Class="WpfApplication2.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<Grid>
</Grid>
</Window>
花花绿绿一大片,还有两个像主页地址的东西....它们都是些什么呢,让我们一个个来分析。
XAML是由XML派生出来的语言,所以有很多在XML中的概念在XAML中是通用的。比如使用标签声明一个元素(每一个元素对应内存中的一个对象)时,需要使用其实标签<tab>和结束标签</tag>,夹在起始标签和终止标签中的XAML代码表示隶属于这个标签的内容。如果没有什么类容隶属于这个标签,可以写为<tag/>。
为了表示同类标签中的某个标签的与众不同,可以给它的特征(Attribute)赋值。为特征赋值的语法如下:
- 非空标签:<tag Attribute1=value1 Attribute2=value2>content</tag>
- 空标签<tag Attribute1=value1 Attribute2=value2/>
在这里有必要把Attribute和Property仔细地辨别一下。
这个词的混淆由来已久,混淆的主要原因是大多数的中文译文里即把Attribute译为“属性”,也把Property译为“属性”,其实,这两个词表示的不是同一个层面上的东西。
Property属于面向对象的范畴。在使用面向对象编程的时候,常常需要对客观事物进行抽象,在把抽象出来的结果封装成类,类中用来表示事物状态的成员就是Property。比如要编写一个模拟赛车的游戏,那么必不可少的就是要对现实的赛车进行抽象,现实中汽车会带很多数据,但是游戏中可能只关心它的长度,宽度,高度,重量,速度等有限的几个数据,同时,还会把汽车的加速,减速等行为提取出来用算法进行模拟,这个过程就是属于抽象(结果是Car这个类)。显然,Car.Length,Car.Height,Car.Weight等表达的是当前汽车所处的一个状态,而Car.Accelerate()、Car.Break()表达的是汽车能做什么。因此,Car.Length,Car.Height就是Property的典型属性,是针对对象而言的。将Property译为“属性”也很贴切,总之一句话:Property属性是针对对象而言的。
Attribute则是编程语言文法层面的东西。比如有两个同类语法的元素A和B,为了表示A与B不完全相同或者A与B的用法上有些区别,这时候就要针对A和B加一些Attribute。也就是说Attribute只与语言层面上的东西有关,与抽象出来的对象没有什么关系。因为Attribute是为了表示“区分”的。所以它译为“特征”。C#中的Attribute就是这种应用的典型例子,我们可以为一个类添加Attribute,这个类里面有很多Property(属性)。显然,Attribute只用来影响类中程序中的用法,而Property 则对应着抽象对象身上的性状,根本不是一个层面上的东西。
习惯上,英语中把标签式语言种表示一个标签特征的“名----值”对称做Attribute。如果恰好有在使用一种标签式语言做面向对象编程,这两个概念就有可能混在一起了。实际上,使用能够使用面向对象编程的标签式语言只是把标签和对象做了一个映射-----针对标签还是叫做Attribute,针对对象还是叫做Property,仍然不是一个层面上的东西。而且,标签的Attribute不是和对象的Property一一映射的,往往一个标签对于的Atrribute大于他所映射对象的Property。
因为XAML是用来在UI上绘制控件的,而控件本身就是面向对象的抽象产物,所以XAML标签中的Atrribute有一大部分和它对应他的对象的Property是一一对应的。当然,这还意味着标签中的Atrribute并不对应它所代表对象的Property。
明白了XAML的格式以及Atrribute和Property之间对应的关系,对上面的代码可谓是一目了然。它的总体结构是一个Window标签内包含一个Grid标签(或者说Grid标签是Window标签的内容),所代表的内容是一个Window对象里面包含一个Grid对象。
<Window>
<Grid>
</Grid>
</Window>
XAML是一种声明式语言,当你看见一个标签,就证明你声明了一个对象,对象之间的关系要么是并列,包含全都体现在标签的关系上。
下面的代码就是<WIndow>标签的Atrribute。
x:Class="WpfApplication2.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300"
前面已经说过,XMAL语言是从XML语言派生而来的。XML有一个功能就是可以在XML文档的标签内使用xmlns特征来定义名称空间(NameSpace),XML也就是XML--NameSpace的缩写。定义名称空间的好处就是,当来源不同的类重名时,可以使用名称空间加以区分。xmlns特征的语法如下:
xmlns[:可选的映射前缀]=“名称空间”
<n:Window x:Class="WpfApplication2.Window2"
xmlns:n="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window2" Height="300" Width="300">
<n:Grid>
</n:Grid>
</n:Window>
XAML中引用外来程序集和其中.NET名称空间的语法和C#是不一样的。在C#中,如果想使用System.Windows.Control名称空间内的Button类,需要先把包含
System.Windows.Control名称空间的程序集PresentationFramework.dll添加引用到项目中,然后再用C#代码顶部写上一句:using System.Windows.Control;。在XAML中做同样的事情也需要添加对程序集的引用,然后再在根元素的起始标签中写上一句:xmlns:c="clr-namespace:System.Windows.Control;assembly=PresentationFramework"。c是映射的前缀,换成其它字符串也可以。因为button来自前缀为c的命名空间,所以在使用button的时候就要使用<c:Button>....</c:Button>。
System.Windows.Control;assemble=PresentationFramework",这么长的一串字符串看上去的确有点恐怖,但不用担心,VS2008中有自动提示功能。
在VS2008自动提示的顶部,你会看到几个像网站地址的几个名称空间,其中就包含例子代码中的那两行。为什么名称空间看上去想一个网站地址呢。其实把它copy到浏览器地址栏尝试跳转也不会打开网页。这里只是XAML解释器的一个硬性编码(hard-coding),只要见到这些固定的字符串,就会把一系列的程序集和程序集中包含的名称空间引入进来。
默认引入的这两个名称空间格外的重要,它们对应的程序集和.net名称空间如下:
http://schemas.microsoft.com/winfx/2006/xaml/presentation对应:
System.Windows;
System.Windows.Automation;
System.Windows.Control;
System.Windows.Control.Primitives;
System.Windows.Data;
System.Windows.Document;
System.Windows.Forms.Intergration;
System.Windows.Ink;
System.Windows.Input;
System.Windows.Media;
System.Windows.Media.Animation;
System.Windows.Media.Effects;
System.Windows.Media.Imaging;
System.Windows.Media.Media3D;
System.Windows.Media.TextFormmatting;
System.Windows.Navigation;
System.Windows.Shapes;
也就是说你可以在XAML中可以直接使用这些CLR名称空间下的类型(因为默认XML名称空间前没有前缀)。
http://schemas.microsoft.com/winfx/2006/xaml则对应一些与XAML语法和编译相关的CLR名称空间, 使用这些名称空间中的类型需要加上前缀x,因为它们被映射到x的XML名称空间中。
从这两个名称空间的名字和它所对应的.NET程序集上,这个不难看出,第一个空间名称对应的是绘制UI相关的程序集,是表示(Presentation)层面上的东西;第二个名称空间则对应着XAML解析处理相关的程序集,是语言层面上的东西。
还剩下x:Class="WpfApplication2.Window2"这个Attribute。x前缀说明这个Attribute来着于x映射的名称空间----前面我们解释过,这个名称空间对应XAML解析功能的。x:Class,顾名思义他与类有一些关系,是何种关系呢,让我们做一个有趣的实验:
x:Class="WpfApplication2.Window2"这个Attribute删掉,再到Windows.xaml.cs文件里,把构造中对InitalizeComponent方法的调用也删掉。编译程序,你会发现,程序依然可以运行,为什么呢?打开App.xaml这个文件,你会发现这样一个Attribute------StartupUri="Window1.xaml",是它告诉编译器把Window1.xaml作为程序启动的主窗体。也就是说,只要Windows1.Xaml能够被解析为一个窗体,程序就能够运行。
x:Class="WpfApplication2.Window2"这个Attribute(不恢复InitalizeComponent方法的调用)。编译之后仍然可以运行,这是使用IL Disassembler(中间语言凡编译器)打开项目的编译结果,你会发现在由项目编译生成的程序集里面包含一个名为Window2的类,如下图所示
这说明,XAML这个Attribute的作用是当XAML解析器将它的标签解析成C#类之后,这个类的类名是什么。这里已经触碰到XAML的本质。前面我们已经看到,事例代码的结构就是使用XAML语言直观的告诉我们,当前的窗体是一个<Window>里面嵌入了一个<Grid>。如果是使用C#完成同样的设计呢?显然,我们不可能去更改Window这个类,我们能做的是从Window派生一个类,再为这个类添加一个Grid类型的字段,然后把这个字段初始化的时候赋值给派生类的类容属性。代码看起来大概是这样:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace WpfApplication2
{
/// <summary>
/// Window2.xaml 的交互逻辑
/// </summary>
public partial class Window2 : Window
{
private Grid grid;
public Window2()
{
grid = new Grid();
this.Content = grid;
}
}
}
最后让让我回到最初的代码。你可能会问:在XAML里面有 x:Class="WpfApplication2.Window2",在Windows2.xaml.cs里面也声明了Window2这个类,难道他们不会冲突吗?仔细看看Windows2.xaml.cs中Window2的声明就知道了----在声明的时候使用的是partial关键字,这样,这样由XAML中解析成的类和C#文件里面定义的部分就合二为1了,正是由于这种partial机制,我们可以把逻辑代码留在.cs文件里,用C#语言来实现,而把那些声明及布局UI元素的代码分离出去,实现UI和逻辑分离,并且,用于绘制UI的代码(如声明控件类型的字段,设置它们的外观和布局等)也不必再使用C#语言,使用XAML和XAML编辑工具就可以轻松搞定。
至此,你应该对这个简单的XAML程序了然于胸了。