深入浅出
WPF
(
2
)
——
解剖最简单的
GUI
程序
小序:
从这篇文章开始,我们进行
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,如需转载请自行联系原作者