WPF系列(1)WPF和XAML基础
终于下定决心开始更新WPF一个系列的文章,这里主要是出于两个目的,一是自己对所学的知识有一个系统的总结,二十希望能对其他人有些帮助,如果您觉得我写的不好,欢迎提意见。
那么既然我要开始写WPF,那我们就开始说说WPF的概念
什么是WPF?其实我们这里不想讲太多的WPF的概念,因为我们发现不管是MSDN,还是一些介绍WPF的书籍开篇介绍WPF的时候无不介绍了很多WPF的很多新特性,譬如WPF的布局系统,样式,模板等等,我个人觉得对于一个初学者,这些可能会打乱学习的节奏,所以我这里不准备像一般的书籍上那样介绍,我决定讲了一个系列之后,再回过头来写一篇总结,而不是在初学者什么都不懂的情况下就来一大堆的概念介绍。
我所理解的WPF是微软的新一代桌面图形呈现系统,直接操作DirectX渲染元素,使之具有更丰富的呈现,我们可以看看很多的网络游戏,具有多么绚的效果,利用WPF,我们也可以。利用WPF,我们可以绘制3D图形,实现动画,使用样式定义外观,更强大的数据绑定,当然,更重要的是,它是声明性编程。关于WPF只是简单的说到这里。它使用XAML声明界面,那么用XAML声明界面是什么意思呢。我相信许多人都有winfrom和ASP.NET的开发经验。我们再winfrom时代,所有的界面都是由程序员搞定的。几年前,在我刚开始学程序的时候,很多的java程序员总是笑话我们.net,说我们是控件的搬运工,除了会干干拖拖控件这种低级的技术活外不会点更高级的。当然,这里排除高手构建的系统。很多刚入行的兄弟的确是拖拖控件,然后写点代码,其实我们知道,在我们拖入一个控件的时候如下图
那么其实在Designer.cs文件里面,编译器会帮我们插入一些代码
1 this.button1.Location = new System.Drawing.Point(33, 34); 2 this.button1.Name = "button1"; 3 this.button1.Size = new System.Drawing.Size(168, 23); 4 this.button1.TabIndex = 0; 5 this.button1.Text = "Test Button"; 6 this.button1.UseVisualStyleBackColor = true;
其实就是帮我们实例化了一个button,设置了一些属性比如位置,尺寸,文本等等。那么到了WPF,其实这些声明界面的工作都在一个XML文件中完成,这个XML文件其实就是XAML。
那么什么是XMAL?主要用于构造WPF的界面,用什么构造,其实还是用控件构建,只是这些控件的构造以声明性的方式开始,而不是以代码的方式,当然最后,.net还是会把文件里面声明的控件实例化,它会根据XAML文件构建一个控件树呈现在屏幕上。所以,我们也可以说,XAML是用来实例化.net对象的标记语言。上面我们说了,之前的界面都是由程序人员构建的,而且构建界面和界面的一些后置代码是不能完美的同时进行的。利用XAML,我们可以把界面的设计,绘制工作完全交给美工,然后我们关注特定的业务代码,因为WPF有一个强大的绑定机制和属性记住,所有的控件的状态都可以用数据来驱动,而不用像我们之前的方式那样编码改变对象的属性。原理就像我们的B/S结构的程序,界面由美工完成,我们负责编码。这样团队的协作更加紧密。界面的绘制我们可以使用VS,当然有更加专业的工具,Microsoft Bland.也有许多优秀的第三方工具,大家可以google一下。下面我们来一个最简单的WPF程序,我们从一个最简单的例子来讲解XAML。
在VS中,我们可以新建一个WPF项目,我的VS的版本为2012,我们分析新建的项目的文件结构
- App.config:配置文件,我相信大家都很熟悉
- App.xaml:管理我们的WPF应用程序,总体来说,是一个应用程序类,文件后缀以.XAML结尾,其实它也是有后置代码的,可以处理一些事件来控制应用程序的行为。前面我们的说了,XAML实际上是实例化对象的标记语言,那么我们当然可以使用声明的方式来实例化Application类,然后后置代码控制行为。以后会专门讲application
- MainWindow.xaml:窗体,这里我们暂且叫它为主窗体,因为是不是主窗体是有app.xaml控制的。前面的内容我们不需要知道怎么控制窗体为主窗体,后面会有主要的章节介绍。
这里我先提一点比较偏的面试题,我在一次面试中,我的leader问我XAML最后编译成了什么文件。这里解释一下,XAML会被编译为BAML(二进制应用程序标记语言)。这些BAML作为资源嵌入到最终的DLL或者EXE程序集中,微软对其进行了优化,而且体积更小,运行时可以更快的解析。下面我们来剖析一个窗体来学习使用XAML。
<Window x:Class="WPFSolutionInCnblogs.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> </Grid> </Window>
这里我们可以看到两个标签,一个是Window,一个Grid。按照我们上面所说,其实就是指定一个Window对象,跟XML文档一样,XAML也只能具有一个顶级元素,一般是Window,Page,Application,UserControl或者他们的继承类。注意,我们需要关闭 <Window>标签,跟标准的XML一样,是不是很像HTML文件,其实WPF的设计思想就是吸取了html布局的这一优点,另外,我们可以看到三个属性,这里其实就是设置我们的窗体的一些属性,比如标题,高和宽。Grid为WPF的布局元素,之后会讨论,Grid嵌套在Window标签中,说明在window对象里声明了一个布局对象。使用grid对象在window里面完成布局。
xmlns为名称空间,其实原理跟我们的C#中的命名空间一样的道理,在XML里面有一个名称空间的概念,其实也是为了区分标记的唯一性,我们可以把标记看成我们的类,同样是Person类,为了区分不同的类,我们使用了命名空间,其实这里也是一样的道理,对于一个标记而言,它也需要确定Window标记是属于谁定义的Window标记,可能,我们也会定义一个Window标记,所以XAML解析器需要知道标记位于哪一个名称空间。例如,我们可能会创建一个window类,那么这个时候,我们就要告诉编译器,使用的是哪一个window。而x:可以看成是我们C#中的命名空间别名,它告诉编译器,要使用别名指定的名称空间下的类。那么在标记的开始如果没有声明别名,一致指定http://schemas.microsoft.com/winfx/2006/xaml/presentation名称空间。
- http://schemas.microsoft.com/winfx/2006/xaml/presentation:WPF的核心命名空间,包含了所有的WPF类,包括用于构建WPF界面的控件,前面说了,如果没有指定名称空间前缀,那么整个文档的默认名称都是它
- http://schemas.microsoft.com/winfx/2006/xaml:XAML名称空间。它包含了各种XAML实用的特性,这些特性会影响文档的解释方式,告诉编译器遇到该特性的时候要采取什么行为。
那么我们为什么要使用这两个名称空间呢,原因有两个
- 避免了别的机构使用了相同的名称空间创建不同的基于XAML的语言,因为schemas.microsoft.com归微软所有
- 控件分布在不同的程序集中,如果不把名称空间集中在一起,会使文档变得很混乱
x:Class="WPFSolutionInCnblogs.UserControl1":这里指定后置代码,使用了x:前缀。以为这使用http://schemas.microsoft.com/winfx/2006/xaml名称空间中的Class特性。这个特性告诉解析器使用指定的类名生成一个新部分类。这里就是继承自window。然后跟标记文件的顶部元素生成的类合并为一个类。我们来看看后置代码类
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } }
我们可以看到在构造函数里面有一个InitializeComponent();方法,我们跟踪一些,最后可以看到这个方法调用System.Windows.Application.LoadComponent方法,那么这个方法是做什么的呢,我们看看摘要
// 摘要:
// 加载位于指定统一资源标识符 (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。
其实,就是实例化对象,实例化我们XAML定义的对象。它会从程序集里面提取BAML,并用它构建用户界面,因为前面我们说了,标记实际就是对象的展现形式,所以利用标记构建对象,设置属性,关联事件。这样,就从标记到代码构建出了我们的应用程序。
后置代码及Name和x:name的区别
通过x:class标记我们可以指定xaml的代码后置类,这个前面已经说了。类为部分类,部分类的概念我们很早之前就已经知道。我们可以通过后置引用xaml声明的控件,前提是控件必须标明Name或者x:name属性。我们这里顺便解释一下Name和x:name的区别。这里,很多人面试会问到这个问题。
Name可以唯一标识对象元素,以便我们在后置代码,或者在数据绑定的时候引用该对象,一般来说,控件是具有Name属性的。因为这个属性是定义在FrameworkElement里面的,而且这个属性使用RuntimeNamePropertyAttribute修饰。这个属性的MSDN解释是“指定某类中映射到 XAML x:Name 属性 (Attribute) 的属性 (Property)”。从根本上来说Name的实现还是使用了x:name。那么x:name又是什么呢
x:name:x:name是属于xaml的特性,也就是说它其实是属于xaml的产物。我们再来剖析,其实在开发时,XAML在某种程度上对后置代码是不透明的,因为构建对象的工作被隐藏起来,从根本上将他们就是一串XML,那么我们在后置代码中又需要使用我们再前端声明的对象,所以我们就需要一个机制建立二者之间的联系。我们在.net程序中做得比较多的事情就是实例化一个对象,然后使用这个对象。通过声明使用的呢?就是引用。这里既然说我们在前端声明了那么多的对象,而在后端我们又需要使用对象。所以我们就需要能够指向这些对象的引用。这里就解开了,x:name其实就是声明一个字段,并且这个字段保存对它所指的对象的引用。所以,从后台,我们可以通过引用获取这个对象。当然x:name是需要在名称范围内唯一的,名称范围的概念我们以后再说。
在一般的时候Name和x:name是可以互换的。但是在有些时候,我们WPF对象是没有name属性的,因为不是所有的对象都是继承于FrameworkElement。所以这个时候我们就需要使用x:name。
事件
在前端,WPF使用属性语法来附加事件处理程序。指定事件属性的对象就是事件的侦听者和调用者。事件处理程序在后置代码中。
下面我们完成一个最简单的按钮点击的WPF程序
<Window x:Class="FirstWPF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Name="MyButton" Width="100" Height="30" Content="Check Me!" Click="MyButton_Click_1"></Button> </Grid> </Window>
这里的Grid是布局控件,以后再讲到布局的时候我们会详细的介绍,我们在Grid内部声明了一个Button。设置了一些属性,指定了点击事件。事件处理程序在后置代码中
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void MyButton_Click_1(object sender, RoutedEventArgs e) { MessageBox.Show("Hello WPF!"); } }
当然,这几句代码是很简单的。前面我们对WPF做了最简单的介绍,我个人觉得下一代编程可能会侧重于声明性编程,安卓的界面开发其实也是同样的原理。XAML是微软几个重要开发的前端开发语言。比如Windows8和Windows Phone,当然还有SilverLight。所以,以后的章节。XAML可能介绍的会比较多。