本文的主要内容是自己使用WPF开发以来,本人对相关知识的梳理,仅为个人的总结,对很多事物的理解依然在探索阶段,总会有谬误和更佳的开发方式,如能提出宝贵建议,感激不尽。
虽然本人并不只专注于WPF,还喜欢各类编程语言,但估计在今后1,2年内依然会继续经常使用WPF开发。
想来想去,时间一长,很多东西还是整理并记录下来为好,遂决定从发布此文以后,开始慢慢积累,随着个人知识的扩展和理解,本文可能会不断添加或者更新内容,然而也会尽可能保留对同一概念前后不同的理解。
WPF搞了快一年,除了去年在上家公司主导开发过一个比较大的WPF项目(已经商业化),其他都是小打小闹的软件。在我看来
WPF的优点是:
1. 可以相对比较容易的写出完全定制化的界面。
2.特有的MVVM设计模式可以完美的分离 UI设计(View层) 和 业务逻辑(Model层)。
WPF的缺点是:
1. 学习成本比较高(我指的是真正精通,你和我说就拖拖控件生成个事件写个方法,当个处理工具,那都不用学了)
新的概念非常多,容易混淆,本人耐性还是很好的,但依然常常连续好多天不停的折腾,折腾到开始喷MS为何这样设计。
2. 不能跨平台。过一阵子去研究下Mono。
3. 由于本身的复杂性,BUG有时隐藏的比较深。
4. 坑很多,复杂的项目下很多坑必须得自己跳,官方文档大多数时候并不能解决实际问题,网上搜索的资料参差不齐,很难查到自己想要的,为了解决大坑最开始常常需要去理解一大堆从天而降的概念。国外下载的demo有时会复杂的过分,csdn上的很多源代码又太不专业···我就吐槽下。
目前为止,我依然没有解决的问题:
a. 图片占用内存太大,在图片很多并且实时刷新速度很快的时候,如果不写代码手动释放资源,内存就会暴涨至崩溃。而同样的功能改用Winform实现就只占用很小的内存。
b. 数据虚拟化面板在XP或部分Win7电脑上显示为空白。只能替换为普通面板。
c. 窗体设置为允许透明时,WebBrowser显示为空白。(目前查到和尝试的方案没有完美解决的)我的办法是换成第三方浏览器内核,比如CEF,但是使用 javascript 内外通信又是很麻烦的事情。不然就是放弃窗体透明···
近期又开始使用WPF写一个程序,同时希望能利用这次开发,将之前WPF相关的知识做一个整合,重新理解概念,丢弃不好的,探索更加舒服好用的,并将已知的融会贯通,尽可能用最“优雅”的方式去实现功能。
那么我们开始吧。
WPF相关技术一定要弄清楚的知识点,先列个大纲,按照我个人建议的学习顺序排序。
1. 理解XAML相关窗体设计的原理。
a. 逻辑树结构非常类似HTML,但更加麻烦。
b. 可以使用XamlPad查看可视树结构。
c. 理解Style类似于CSS,并可以通过随时更换资源字典以达到更换主题或者换肤的目的。
2. 触发器(Trigger),最常用的是属性触发器和数据触发器。
a. 需要知道触发器主要是用于视觉交互的。
b. 属性触发器是控件本身的某个属性值发生改变,比如IsMouseOver=True的时候,会触发可视内容 比如背景色 发生变化。
c. 数据触发器是在数据模板(DataTemplete)中,当某个业务数据发生变化改变时,会触发可视内容发生变化。
3. 为了创建形态各异的界面,实现各种神奇的效果,需要学习WPF绘图。
a. 使用图形,包括:直线,矩形,椭圆,贝塞尔曲线,Path(最强大的路径)
b. 应用滤镜效果,Effect比较简单,但是导入和开发外部滤镜,一直没有研究。
c. 使用变形。有平移,旋转,缩放,扭曲等基本变形,以及矩阵变形。(要注意的是:每种变形既可以放在呈现变形中,也可以放在布局变形中,需要区分二者的区别。呈现变形只是看到的样子变化了,实际位置和形状都没变。布局变形是真的变化,会在变形的同时不断对其他控件重新进行布局计算。)
4. 学习使用XAML创建简单的动画
a. 尝试使用3类触发器触发动画的发生
b. 使用VisualStatusManager来应用动画
c. 如无必要,尽量避免通过写代码的方式创建动画
d.(扩展:使用Blend创建并组合出复杂的动画。)
5. 依赖属性和附加属性。
a. 要学会如何自定义我们自己扩展的依赖属性和附加属性。
b. 所谓依赖属性,从功能上讲:就是一个普通的属性,附带了可以绑定到任意对象的其他属性上的功能。所谓绑定就是:一个值变化,另外一个值跟着变化的。这样可以省去大量的界面效果相关的后台代码,并使界面和业务代码分离成为可能。
c. 依赖属性可用于继承一个现有控件或者自定义控件,并为其扩展属性。缺点是这些属性不能复用,是控件自己专属的。
d. 附加属性是一种特殊的依赖属性。有两种用法,一种继承现有类,并进行定义,实现的效果如同Canvas.Left。 一种新建类,主要用于为了不进行大量继承的前提下,给现有控件扩展额外的属性。是一种很好的 组合模式 思维。
6. 模板。主要了解:ControlTemplete,DataTemplete
a. ControlTemplete 是用来重写现有控件的可视结构的,一般和依赖属性和附加属性结合,加上绑定,控件可以获得很好的扩展。
b. DataTemplete 主要用于定义数据对象的可视化结构的。既然是数据对象,最好要有个数据类型,即在DataType中定义。
c. 模板在WPF起着巨大的作用。控件模板可以很容易写出任意形态任意效果的外观,数据模板使得View层和ViewModel层很好的分离。请一定要注意,起初我对这样的概念不屑一顾,其实就是没明白什么意思。后来我才懂:就因为数据模板的存在,使得代码中几乎再也不用出现控件对象了。
d. 推荐模式在模板中的运用,利用自动创建的模板,经常会看到Part_XXX,不明白怎么回事? 看基类的特性中,会有TemplatePart,这其实是一种推荐的设计模式,用于提示后来的开发者,告诉你控件组成的必要元素:名称和类型。也就是说,你可以重写控件模板,但是如果要实现控件自身的核心功能,一定要保留一个名为Part_XXX的某个类型的控件才行。
7. MVVM设计模式,最方便学习此模式的是MVVMLight框架,可以直接在NuGet中下载。
a. Model - View - ViewModel。不同于MVC,MVP等设计模式, MVVM最主要的特点是实现UI(View)和业务(Model)的分离。而ViewModel应该同时负责表现逻辑和业务逻辑。这在开发时尤其有用,另外可以同时快速创建设计用的ViewModel,以便设计阶段即可以模拟出完全真实的使用效果,因为View层对应的ViewModel可以很容易的切换。
b. 本人并非设计模式的过渡崇拜者,然而只要让代码生产力持续保持比较高的效率,就是好的方法。很多时候只要不影响大局,在CodeBehind中写一些代码是无可厚非的。
c. MVVMLight框架下,有很好用的EventToCommand,可以将任何事件直接转化为命令,在我看来这使得UI和业务分离的更加彻底。
d. MVVMLight下的消息机制,Messenger 也是个很好用的东东,可以实现本无关联的ViewModel间的通信,让他们继续无关联下去。另外也可以用于导航。
e. ViewModel 让 Model 更加适合于 View。注意两点:一. ViewModel 不需要知道 View 中有什么,换句话说,ViewModel 中不要引用View中的控件。 二. View 中不要直接引用 Model,而是借助ViewModel,想要用Model怎么做? 比如:PersionViewModel 里有个公有的 Persion 类型的属性 persion, View 的DataContext是 PersionViewModel,绑定时写 {Binding Path=persion.Name} 即可。
8. 可以附加在控件上的 行为 (Behavior)
a. 行为,还有上面刚说的 触发器,EventToCommand,都是一种附加属性。这样就很好理解了。通常理解的附加属性,只是一一个数据类型的值,既然扩展成了一个行为这么复杂的数据类型,目的主要是为了实现各种行为效果的重用,然而行为的封装是最完整的。
9. 要反复深刻理解装饰器相关 Adorner, Decorator, AdornerDecorator
a. 从简单开始,最好懂的是 Decorator,如果这个词感到陌生,那么Border就不陌生了,边框嘛,一个东西,外面套个边而已。
然而Decorator是Border的基类而已。可以扩展它,然而我目前还没有遇到继承Decorator的应用场景。
b. 再说Adorner,这才是真正的装饰器,本身没有可视结构,它的存在就是要你继承它,并设计它的可视样式,如何继承,网上一大堆教程,最直观的理解就是:在控件自身上面,蒙了一层额外的装饰,用于交互性的提示与操作。 比如:光标,旋转钮,调整大小,表头的排序箭头 等等。需要什么就画什么,恩恩,这个很重要。
c. 理解了前面两个,再来说最后一个,看起来好复杂好高大上的样子, 其实很简单, 按我的理解: 这就是一个双面胶,用来粘贴 Decorator 和 Adorner。微软需要在控件上面有个容器,用来放 Adorner,这个容器最好从控件的最边缘开始,控件的最边缘,自然是 Decorator了,OK,那么再包裹一个东西,就可以放 Adorner了, 于是这个东西的名字叫做 AdornerDecorator,上面有个叫做 AdornerLayer的东西可以让Adorner直接Add。
d. 事实上,只有AdornerDecorator和ScrollContentPresenter具有 AdornerLayer,所以,想要自己加装饰器的时候,不要忘记在模板里面套一个 AdornerDeocrator 哦。尤其像我这种经常写自定义窗体的,窗体的 ContentPresenter 外面套个 AdornerDecorator 是 必不能忘的。
10. 路由事件和命令
a. 如果想要写出来的WPF程序在复杂的界面中不会出现莫名其妙的问题,那么一定要弄懂路由事件和路由命令的概念。
所谓路由,有三种烂大街的方式:隧道,冒泡,直接。 顾名思义:隧道,标签从外向内响应事件。 冒泡,标签从内向外相应事件。
直接,只能从定义元素监听元素的直接事件,写别的地方没用。
b. 路由事件是定义在哪一层监听事件,而不是点击哪一层会执行事件,目的自然是 “一夫当关万夫莫开”,比如冒泡路由,N多的控件都可能响应某一鼠标事件,那么只需要在集合控件上面 监听这一事件就可以了(附加事件)。 在 响应事件处理中,再拿到实际命中的 控件就可以了。 当然,这个时候我往往是直接取命中控件的DataContext,直接转化为ViewModel,操作数据层。
c. 其实这个路由事件机制和HTML中 javascript 的事件机制非常非常类似,addEventListener的第三个参数 useCapture 用来控制 是否是 Bubbling(冒泡)方式。我也常常会用 jQuery 中的 delegate 来做类似的事情,以便监听未来产生的子元素相关的事件。
d. 可以在路由事件中标记 Handler = True。来阻止事件继续路由下去。 然而 实际上 即使这样设置,路由事件依然在继续传递。 因为用 AddHandler 显式 挂接路由事件处理程序,可以定义路由事件即使被处理依然会执行。即设置 handledEventsToo = True。 这里分析,相信 Handler || handledEventsToo 为真时,即会执行事件处理程序。
e. 自定义路由事件,类似于注册依赖属性。其中可以定义路由类型等等。
f. 说来说去,需要强调的是, Preview名称开头的事件是隧道方式,代表了事件是从外向内传递的。不带Preview的事件是冒泡方式,是从内向外传递的。 同一名称的事件Preview先执行。这是个形象的反弹,球弹进去再弹出来,这个过程球可以被人抓住。
g. 路由命令在我看来,因其天生的缺陷与限制,应用的比较少。我一般使用 MVVMLight 的 RelayCommand,当然,如果项目没必要引入MVVMLight时,自己自定义也可以。
讲了半天的理论,都是这不到一年WPF开发的体会,接下来开始实践内容部分。以后自己实现了感觉不错的东西,就会列在下面分享。