小序:
从这篇文章开始,我们进行 XAML 语言的上手学习。说实话, XAML 这种语言一点也不难。如果把 XML 看成是 “ 父类 ” ,那么 XAML 就是 XML 的一个派生类了,所以 XML 的概念在 XAML 中是通用的。 What ?你不了解 XML ?没关系,反正我们是从头开始!
正文:
你还能想起学 C/C++ 的时候写的第一个程序吗?呵呵,一定是 “Hello World” 吧!今天我们来解析一个 “Hello WPF” 。准备好了吗? Let's go !
准备知识
使用 VS2008 新建一个 WPF Application ,你立刻就会得到一个看上去是 “ 空 ” 的窗体。窗体这个东西,在 Windows Form 程序里叫 “Form” ,在 WPF 里叫 “ Window ”—— 喔, Win32 API 里也叫 Window !是的,你说对了, WPF 在某种程度上是向 Win32 API 的 “ 返璞归真 ” !
为什么说它 “ 看上去 ” 是空的呢?实际上,这个 Window 的内部有一个叫 <Grid> 的 元素( Element ) ,只是这个元素是看不见的,它就像信纸上的 “ 暗格 ” 一样。
针对 XAML 文件,是可以进行 “ 所见即所得 ” 的可视化设计的。你在 XAML 代码上做的修改,只要是合乎语法的,那么在设计器里就会立刻反映出来(有时候需要刷新一下)。如果你发现设计器里显示不出来了,那一定是 XAML 语句出了问题,最好想办法修正它。不然的话,在设计器里都看不到效果、只能运行起来看,这还算什么可视化编程呢?要 XAML 还有什么意义呢?
在我们正式剖析代码之前,让我们牢记两件事:
1. 这个世界是一个 “ 组合 ” 的世界 —— 汽车是由一个车身和四个轮子组合成的;飞机是由机翅和机身组合成的。这些组成部分,我们称之为 元素( Element ) 。
2. 在 XAML 文件里,每写一个元素就相当于定义了一个元素所对应的 .NET Framework 类的实例。
有必要强调一点 :如果一个实体是由一些(同类或者不同类的)子对象组合成的,我们就称这个实体为 “ 父元素 ” 、称这些子对象为 “ 子元素 ” ,因为父元素包含着子元素,所以常把父元素称为 “ 包含元素 ” 、把子元素称为 “ 被包含元素 ” 或父元素的 “ 内容 ”—— 我们需要注意,被包含元素并不等同于包含元素的属性( property ),被包含元素只是包含元素的一个部分 。 初听这句话,肯定是一头雾水, OK ,让我举个两个例子。比如有一个班级,这个班由 56 个学生、 1 个老师、 60 张桌子、 70 把椅子组成,那么这些学生、老师、桌子和椅子,只是这个班级的一些 “ 组成部分 ” ;而这个班级的人数、班级隶属的年级、班级的编号是这个班级的属性。再比如我有一个 Window ,这个 Window 里有 1 个 Grid ,这个 Grid 里又包含着 3 个 TextBox 、 2 个 Button ,那么这 1 个 Grid 就是这个 Window 的子元素, 3 个 TextBox 和 2 个 Button 又是 Grid 的子元素;而 Window 的 Name 、 Icon 、尺寸乃至 Resources 都是这个 Window 的属性。
你可能会问,这个道理这么简单,有什么好强调的呀?
原因是这样的:对于 C# 的类而言,属性( property )肯定是一个对象(比如 Window 的 Name 属性,它就是一个 String 类型的对象),这个对象也是类实例的一个组成部分;而在对这个类进行扩展的时候(对这个类进行派生),我们新添加进来的元素(比如 3 个 TextBox 和 2 个 Button )也是类实例的组成部分。 OK ,大家看到了,从现实世界抽象到编程世界来之后,它们的区别就不那么鲜明了。为了再让它们的区别 “ 鲜明 ” 起来,请大家记住两句话:
- 属性对象(元素)是父元素所固有的,子元素则可由设计人员来进行增减
- 属性对象(元素)是隶属于父类的(从父类继承而来),子元素是在设计派生类时新添加进来的
之所以在剖析代码之前讲述这些东西,是因为 XAML 是一种 XML 语言,它的语法完全是元素嵌套组合式的,而属性和子元素也都是类实例的组合体,如果不先分清楚,读代码的时候一定会感觉混乱。
在了解了这些内容之后,我们就可以放心地读代码了。
剖析代码
请新建一个名为 HelloWPF 的 WPF Application 项目。在 XAML 语言编辑器里,你会看到和下面一样的代码。
< Window x:Class ="HelloWPF.Window1"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title ="Window1" Height ="300" Width ="300" >
< Grid >
</ Grid >
</ Window >
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
Title ="Window1" Height ="300" Width ="300" >
< Grid >
</ Grid >
</ Window > 让我们一个词一个词地分析这段代码。
就像我们遇到复杂长句时先要分析它的主干一样 —— 让我们暂时抛开花花绿绿的代码,看看这段程序的主干是什么。喔 ~~~ 这段程序的主干是如此的简单!就是一个 < Window > 元素里包含着一个 < Grid > 元素。
< Window >
< Grid >
</ Grid >
</ Window >
< Grid >
</ Grid >
</ Window > 一个句子的主干找出来之后,句子的大意也就明白了。我们已经得到这段程序的主干了,那这段程序说的是什么呢?前面我叮嘱大家一定要记住两件事件。其中一件就是 “ 见到元素就相当于创建实例 ” 。我想你一定会说:这段程序就是在定义一个 Window 类的实例,这个实例的一个组成部分是一个 Grid 类的实例。
呵呵,对于这个答案,既可以说它是对的,也可以说它是错的,为什么呢?
请注意, Window 元素的一个 attribute 是 x : Class ="HelloWPF.Window1" ,这个 Class 就是在告诉我们 “ 嘿!本 XAML 文件实际上是这个类的 UI 部分哦! ” 。本例中,类名就是等号后面的 “HelloWPF.Window1” ,也就是说,是 HelloWPF 名称空间中的 Window1 这个类。在项目浏览器中找到与这个 XAML 文件配套的 C# 文件( XAML 文件名 .cs ),果然能找到这个类。
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
{
public Window1()
{
InitializeComponent();
}
}
从这个角度来看,上面的答案就是错的了 —— 因为这段代码是在定义一个 Window1 的实例会是什么样子。
那为什么又可以说它的对的呢?显然, Window1 是 Window 类的派生类,根据多态的原理,你说 Window1“ 是一个 ”Window 并没有错 —— 就像你说 “ 鸭子是一只鸟 ” 一样正确。在派生过程中,我们使用 <Grid> 标签为它添加了一个 Grid 类型的 UI 成员 —— 派生吗,一定是要做些扩展的。
看到这儿,我想你已经猜到了, XAML 文件就是用来定义 Window1 这个类的 UI 部分(一旦这个类创建了实例,那这个实例的 UI 将与 XAML 代码的描述相一致)。微软通过 XAML 语言把 UI 设计完全暴露给了我们,让设计师可以像设计网页一样来设计桌面程序的界面。至于这个类的逻辑部分,还是用传统的 C# 语言来实现。这样,设计人员和开发人员就能各司其职、协同工作了。
一个类能够 “ 掰成两半 ” 来写,这要归功于 partial 这个关键字,使用这个关键字,可以把一个类的代码分散在多处来实现。可问题又来了 ——XAML 代码怎么和 C# 代码 “ 对接 ” 啊?呵呵,这个还真不用咱们操心,微软的 XAML 解析器本着 “ 进村悄悄地,开枪地不要 ” 原则,在背后把这件事完成了。因为 XAML 代码中没有逻辑,所以,解析 XAML 的大部分工作就是按照元素标签的描述把对象创建出来 —— 比如,解析器见到有 <Grid> 标签出现,就会生成与 C# 代码 new Grid() 等价的代码。
喘口气儿 ……
让我们继续。
让我们继续。
XAML 名称空间
如果你问一个初学 XAML 的人(碰巧他还没有 XML 编程经验):最让他迷惑的是什么?我想他会告诉是: “ 就是那个 x ! ” 老实讲,我就是他们中的一员,初学的时候我也很 “ 痛恨 ” 那个 x 。一会儿是 “:x” ,一会儿是 “x:”…… 这个 x 到底是什么呢?
其实非常简单 —— 这个 x 是一个名称空间、一个使用 XML 语法声明的名称空间 。只是 XML 语言声明名称空间的时候语法比较怪而已。下面,让我一一为你解释。
首先,如果你使用 C# ,那么你对这几句代码一定不陌生:
这是对 .NET Framework 类库中名称空间的引用。一旦引用了这些名称空间,在使用这些名称空间中的类时就不并在再类名前加上长长的前缀了。
请大家考虑这样一种情况:有两个很长的名称空间,我需要使用它们中的类,但不巧的是这两个名称空间里的类又有很多是重名的 …… 怎么办呢?呵呵,我们可以使用名称空间的别名来解决这个问题:
using Software = Microsoft.Google.Adobe.RedHat.CA;
using Hardware = IBM.Sun.RedHat.Dell.Lenovo.HP.Oracle;
using Hardware = IBM.Sun.RedHat.Dell.Lenovo.HP.Oracle; 这样,即解决了输入字符过多的问题,又解决了类名冲突的问题:
Software.Company c1 = new Software.Company();
Hardware.Company c2 = new Hardware.Company();
Software.Company c1 = new Software.Company();
Hardware.Company c2 = new Hardware.Company(); XAML 名称空间跟 C# 的名称空间别名类似,但不完全一样。先让我们看那个 x 。 x 其实就是一个简写的名称空间啦! xmlns 就是 XML Namespace 的简写,意思是要声明一个名称空间。
这句话的意思就是:声明一个名为 x 的名称空间( xmlns 与名称空间的名字间用冒号隔开)。后面为什么要跟一个 “ 网址 ” 呢?呵呵,我们都被骗了 —— 那根本不是一个网址,不信你用 IE 试试。其实,它就是一个普通的字符串,你尽可以把它当成 “Microsoft.WinFX.XAML” 来理解。但值得注意的一点是:这个字符串不只代表着一个名称空间,而是代表了一组名称空间,这组名称空间合称 “XAML 语言空间 ”—— 因此,它的名字是 x 。换句话说,这个 x 相当于一下子引用了好几个名称空间进来,这几个名称空间在 .NET Framework 里都能查到,包含这些名称空间里的类都是与 XAML 语言的语法、特性、功能有关的。
在 XAML 中,想使用某个名称空间里的类就要使用 “ 名称空间 + 冒号 + 类名 ” 的格式,所以:
x : Class 的意思是使用 x 名称空间里名为 Class 的类。类似地,以后我们还会看到 x:Static 、 x:Type 、 x:XData 等等,这都是在使用 x 这个名称空间里的类。
x : Class 的意思是使用 x 名称空间里名为 Class 的类。类似地,以后我们还会看到 x:Static 、 x:Type 、 x:XData 等等,这都是在使用 x 这个名称空间里的类。
与声明 x 名称空间类似,这儿还有一句:
xmlns =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] "
xmlns =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] "
这回的 “ 网址 ” 与前面的不一样,最后一个词是 “presentation” ,顾名思义,这回引用进来的一组名称空间一定是与显示相关的。说对了!比如 System.Window.Control 这个 .NET Framework 的名称空间就包含在里面,这个名称空间里几乎包含了所有 WPF 的 UI 元素(在 WPF 里,我们称控件为 UI 元素 )。
你可能会问:这不是在声明名称空间吗!名字哪儿去了?
问的非常好!当 xmlns 后面没有跟随名称空间的名字时,就相当于省去了名称空间的名字,当使用这个名称空间中的类时就无需再加前缀(根本没前缀可加,怎么加?)。换句话说,当一个类名前面没有前缀时, “ 默认 ” 就是此名称空间里的类。因此,它称为 “ 默认名称空间 ” 。这个用法跟 using System 差不多。 BTW :默认名称空间只能有一个。
问的非常好!当 xmlns 后面没有跟随名称空间的名字时,就相当于省去了名称空间的名字,当使用这个名称空间中的类时就无需再加前缀(根本没前缀可加,怎么加?)。换句话说,当一个类名前面没有前缀时, “ 默认 ” 就是此名称空间里的类。因此,它称为 “ 默认名称空间 ” 。这个用法跟 using System 差不多。 BTW :默认名称空间只能有一个。
大家可以动手试试这样做,把 xmlns =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] " 改成 xmlns:n =" [url]http://schemas.microsoft.com/winfx/2006/xaml/presentation[/url] ", 这时候程序就编译不过去了。当你把后面的 <Grid> 元素改成 <n:Grid> 和 </n:Grid> 后,就又可以通过编译了。
最后, Title ="Window1" Height ="300" Width ="300" 的意思是设置 Window1 类(也可以说是 Window 类)的几个实例属性。这种语法称为 “ 使用标签的 attribute 设置对象的 property” ,碰巧, attribute 和 property 这两个词都被译为了 “ 属性 ” ,所以这句话就没法翻译了。除了使用 attribute 设置对象 property 的语法外, XAML 还支持使用子元素方式设置元素属性的语法。下面这段代码与原代码是等价的:
<Window x:Class="HelloWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Title>Window1</Window.Title>
<Window.Height>300</Window.Height>
<Window.Width>300</Window.Width>
<Grid>
</Grid>
</Window>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Title>Window1</Window.Title>
<Window.Height>300</Window.Height>
<Window.Width>300</Window.Width>
<Grid>
</Grid>
</Window> 大家注意啦! <Window.Title> 、 <Window.Height> 、 <Window.Width> 叫做 “ 属性元素 ” ,表示它虽然是一个子元素,但它是父元素的一个属性;而 <Grid> 则是一个普通元素,而非 <Window> 元素的属性 —— 它们虽然都是 <Window> 的组成元素,但不是一个圈子里的(请跳转到上面,看看准备知识)。总有初学者问我: “ 反正 Title 也是 Window 的一个组成部分,能不能写成 < Title > Window1 </ Title > 啊? ” 幽默点讲, XAML 解析器没那么聪明;地道的说法是,从物理上讲,并没有 <Title> 这个 UI 元素;从 XAML 语法上讲,这样会造成语义上的含混、远不及 <Window.Title> 来得清晰。
啰嗦一句:当对象的 property 用一个简单的 string 就能描述清楚时,完全没必要使用子元素式语法小题大作。当对象的属性是一个复杂的对象时(你想用 attribute 式语法都办不到),再使用子元素式语法。
到此,一个最简单的 WPF 程序(的 XAML 部分)就算分析完了。本文成于仓促,之间有不少不严谨的地方,我会慢慢修改。大家有什么好的建议,请在文后盖楼。
唉 ~~~ 看来今天是 Hello 不了 WPF 鸟,以后再说吧 ~~~
本文转自 水之真谛 51CTO博客,原文链接:http://blog.51cto.com/liutiemeng/95263,如需转载请自行联系原作者