WPF简览
离开Flex开发已经快两年了,从RIA刚开始兴起就一直做Flex,当初热火朝天的场面还历历在目,往事如斯,只能感叹IT技术变化之快!这两年用WPF开发Windows桌面应用,一开始只是需要它,随用随查而已,没有真正看。最近才有点时间,准备好好学习学习,本来想把WPF和Flex做一个对比,但是想想这样可能会比较杂,所有决定就WPF单独写一篇文章吧。今天先写WPF的 ,然后再写Android、Flex、Java Swing,以纪念这些年使用的技术!
运行环境
WPF用于开发Windows桌面应用,只能在Windows平台运行。当初RIA兴起的时候,微软也提出了一个RIA方案,叫做Silverlight,是WPF的缩减版,可以跑在IE浏览器上,当然也不跨平台。而现在移动互联网的风潮一起,带来了Windows 8和Windows phone 8,Metro App出现了,虽然开发环境不是WPF,API也重写了,但是设计思想和WPF差不多。
开发语言
WPF主要用C#开发,为了开发视图的方便,Xaml标签语言被加入进来。所以用Xaml设计视图,用C#写视图之外一些逻辑什么的。此外WPF可以直接调用C++ Managed库, 也可以调用Win32函数,这样一些C#不容易实现的功能,就可以封装到C++库里,方便调用。
C#有一些特性挺实用,比如代理,相对于Java来说,不知道灵活多少倍;在.NetFramework 4.5中还新增了异步操作,不过还没怎么用。
线程模型
WPF支持多线程,一个WPF程序启动来之后,什么都不干,就会有两个线程:绘制线程、UI线程。
首先来看绘制线程,如果业务逻辑导致视图发生变化,绘制操作就会扔到一个队列里,绘制线程从队列读取操作,进行绘制。但是从开发者角度,我们是看不见这个绘制线程的,当然也控制不了,所以如果您想知道特定的一次绘制什么时候完成,基本没戏。
UI线程就是所谓的主线程、 只有在这个线程内才能进行视图的操作。所以为了保证视图的反应速度,一般很长时间才能完成的任务,最好分配到其他线程,完成后再通知主线程更新UI。Android的线程模型也是这样,只不过,在和主线程通信上,WPF和Android的实现方案不一样,C#的代理,这个时候派上了用场,极大的提高了开发效率。
视图绘制
WPF建立在DirectX 之上,根据GPU支持DirectX的不同,尽可能多地采用硬件加速。WPF把视图渲染的硬件加速情况分为三个级别:
0:全部用CPU渲染视图;
1:部分用CPU,部分用GPU;
2:全部用GPU。
Visual对象为WPF提供了绘制支持,它储存了一个绘制操作列表,当属性改变的时候,如果影响到了视图显示,该操作列表会被更新,但是不会立即绘制,而是把操作列表仍给绘制线程,因为有可能绘制线程可能还有其他绘制操作,所以不会马上绘制,WPF也会优化这些绘制操作,减少重绘。
绘制顺序: 视图绘制从位于可视化树中最顶层节点中的可视化元素开始遍历,然后将按照从左到右的顺序遍历它的子级。如果某个可视化元素有子级,则将先遍历该可视化元素的子级,然后再遍历其同级。也就是说绘制顺序是先父亲后孩子,先左后右。
视图布局
布局分为两个阶段:Measure和定位。Measure用来决定视图的尺寸以及子视图的尺寸。在定位阶段,父视图根据测量结果来决定每个子视图的位置和最终尺寸。WPF中每一容器都提供了自己的MeasureOverride和ArrangeOverride方法来实现自己的布局行为。Measure阶段的最终目的是得到所有子控件的DesiredSize, 它将被用于定位。
具体到代码,可参考如下实现:
protected override Size MeasureOverride(Size availableSize) { Size panelDesiredSize = new Size(); foreach (UIElement child in InternalChildren) { child.Measure(availableSize); panelDesiredSize = child.DesiredSize; } return panelDesiredSize ; } protected override Size ArrangeOverride(Size finalSize) { foreach (UIElement child in InternalChildren) { double x = 50; double y = 50; child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); } return finalSize; // Returns the final Arranged size }
风格和外观
WPF用ControlTemplate来定义控件的外观,用Style改变控件的属性或者设置触发。这些都挺好,受不了的是Xaml的纵向排列风格,如果Style定义太多,或者ControlTemplate层次多一点,太难看,我这眼神真受不了,怀念Flex的简洁啊。并且WPF的State支持太差,和Flex相比,差的十万八千里。
控件结构
WPF控件设计采用了MVVM模式,它是MVC的进化版本,去掉Controller,加入了ViewModel,可以把ViewModel认为是写视图逻辑的地方,这样就可以很好的理解了。每一个控件都有它的数据模型DataContext, 可以使用数据绑定来更新视图,也可以用DataTemplate来定义怎么显示数据。此外,不能不提Command,控件的动作绑定到Command,使得ViewModel能够响应视图的动作,并且使得视图和ViewModel更没有互相可见的必要了。
事件模型
WPF事件派发也是一个U字形,支持Tunneling、Direct、Bubbling(对应Flex的Capturing、Targeting、Bubbling),不过我用WPF的事件很少,主要是用Action,为什么呢? 可能是因为我懒吧,新建一个事件,首先需要定义这个事件(一大长段代码),其次需要创建对应的事件参数类,当我真正需要派发这个事件时候还需要构造这个参数对象。太多繁杂的代码,可能别人没这个感觉,当你用过Flex,就知道我的心情啦!说到这里,就多说几句,因为之前我没在Windows平台开发过,以前都是Java啊, As3啊。后来,用C#开发WPF,也接触到其他微软的技术、文档、API。总的感觉文档不友好、API巨复杂,干一点小事,就需要写N多代码,对于微软这些人我只能无语、膜拜了!
控件生命周期
WPF控件有三个生命阶段: Initialized、Loaded、UnLoaded,它们对应三个事件。
Initialized发生在控件被构造完成的时候,这个时候控件的一些属性都被赋值了,xaml定义的子视图也被初始化了,但是一些动态属性,比如数据绑定的还没有,因为本来数据绑定的不知道什么时候有数据。
当控件布局完成,正准备绘制的时候,Loaded事件被激发。Loaded事件可以发多次,比如控件被移除,然后又加入到视图列表中。
Unloaded被激发的时候,就是控件被移除的时候,这个时候控件的父亲已经不存在了。
总结:
总的来说,WPF还是不错的,Xaml里不能写代码,少了灵活性,但是保证了MVVM模式的更好运用。此外,WPF的绘制为开发隐藏了很多复杂性,但是隐藏的太封闭,加入一些可定制接口就更好啦。