WPF 是一个界面层框架技术,要对 WPF 技术达到熟练运用的程度,需要同时拥有开发和设计两方面的知识。而我作为一名开发人员,以前的总结都是站在开发人员的角度,今天这篇博文则期望更多地站在设计人员的角度来进行总结。其实,开发人员比较难理解WPF 框架中为什么会提出 Style、Template、Command、State、StoryBoard、Trigger 等这些概念,但是当你看一看 Flash 或者 PhotoShop 的设计人员平时的工作,就会发现原来许多概念早已是他们的常识,而 .NET 只是把这些概念在 WPF 框架上加以实现而已。
最近接了一个 WPF 的活,对方要求我按照他们美工所画的图,使用 WPF 技术构建一模一样的用户界面。目前项目已经结束,也收到了约定的劳务费用。由于做得还不错,所以他们又和我约定了两个更复杂的项目。其实我个人的 WPF 技术并不高,所以接这个活的一部分原因还是期望通过设计实际的 WPF 项目,来锻炼自己的 WPF 技术。而本篇博文和之前的 WPF 总结不同,主要是想简洁地总结一下项目中的 WPF 实战经验。也就是说,一是只涉及这个项目中用到的概念,而不是所有 WPF 中的概念;二是不会把某个概念技术说透,只从设计人员的角度去讲使用方法。
Template
模板是一个可视化控件结构定义,也就是最终界面显示的可视树中控件结构。主要分为两个,一个是 DataTemplate,一个是 ControlTemplate。
DataTemplate 用于为某一类数据定义可视化控件结构。而 ControlTemplate 则是为某一种类型的逻辑控件定义可视化控件结构。一般情况下,使用 ControlTemplate 的场景要远远多过 DataTemplate。
那么如何设计一个 ControlTemplate 中的控件结构呢?其实分两步,第一步,设计这个控件的静态结构;第二步,设计控件的动态行为。其实都很简单,使用 Microsoft Expression Blend 这个专业的 WPF/Silverlight 设计工具进行界面设计,拖拖拽拽就搞定了。
这里要注意的是可视树中的动态行为。主要有两种,一种是模板内部根据各可视控件状态变化而变化的属性设置,可以直接编写在 ControlTemplate 的 Triggers 中,Blend 中则可以直接在 Trigger 面板中进行设计;而另一种行为则需要通过与外层逻辑控件的交互完成。交互的方式有:直接绑定逻辑控件属性、路由命令、路由事件、PART_设计约定。
后三种方式是必须要编写代码才能完成的行为。虽然它们并不是设计人员的工作,但是它们是连接开发与设计的桥梁,鉴于它们的重要性,这里还是专门说明一下:
- 路由事件
在设计自定义逻辑控件时,可以在类型的静态构造器中使用 EventManager.RegisterClassHandler 来处理内部可视树中所有元素的路由事件。举个简单的例子:在 Button 类型的设计代码中,为 LeftMouseButtonDown 事件注册了处理函数,并转换为自己的 Click 事件,这样,点击 Button 内部所有可视控件时,才会触发 Button 的 Click 事件。
这是一种逻辑控件主动去处理或转换可视控件行为的方式。 - 路由命令
我认为这是一种可视控件主动挑选命令,而逻辑控件被动执行命令调用的方式。
机制是这样的:控件开发人员为逻辑控件设计了相应的一些行为,但是他们并不知道设计人员会在可视树中用哪一个具体的元素来执行这个行为。这时,开发人员为逻辑控件编写一个路由命令,并在类型静态构造器中为该命令注册处理函数执行相应的控件逻辑。设计人员则只需要在设计控件模板时,为具体元素设置 Command 即可。这样,由于命令也是通过路由事件来进行路由的,所以内部的可视树控件执行命令时,会一直路由到上层的逻辑控件上,并被相应的逻辑处理。达到可视树控件与逻辑控件交互的效果。 - PART_ 逻辑控件设计约定
当开发一个自定义控件时,如果知道这个控件对应的模板中,必须要有一个某一类型控件,这时我们就可以要求模板设计人员必须在模板中添加该类型的控件,并以一个固定的名称命名。这样,开发人员就能在逻辑控件的 ApplyTemplate 方法中通过 Template.Find 找到对应的控件,然后就可以对它进行事件监听、属性控制等操作。而连接逻辑控件、模板中可视树控件的那个名字,为了和一般的命名区分开并显示其重要性,需要使用“PART_” 起头。
例如,ComboBox 就在类型设计时,指定了至少需要以下两个控件,才能发生正常的下拉行为:
Style
样式本质上是对控件的一组属性设置集合。
当我们设计好一个 Style 后,可以把它应用到对应控件的许多实例上,那么就算是通过 Style 默认设置好了这些属性。另外,Style 还提供了 Trigger,可以实现简单地属性变更时设置其它属性的功能。一般较少使用到 EventTrigger。
Style 中我们常常看到的最长的一个属性设置就是设置 Template 属性,即控件的模板。虽然他们俩往往出现在一起,但是 Style 跟 Template 其实没有直接的关系,Style 所做的只是简单地设置一下控件的 Template 属性值而已。
有些朋友会问:要达到同样一个效果,我们也可以在 Template 中直接设置视觉控件的属性,例如直接设置边框宽度。那么,为什么还要把一些属性设置编写在 Style 中,再去让 Template 中的控件进行模板绑定,这不是太绕了吗?其实,这样做的好处是使得模板中视觉控件的属性值不会被写成固定值,可以随着外层逻辑控件属性值的变化而变化。这样,当我们直接给逻辑控件设置边框宽度时(本地值),模板中的可视控件就会使用这个更高优先级的值来显示边框。
自定义控件
在开发实际项目时,一般都会遇到要开发自定义控件的情况。相关内容上面已经都谈到了,其实挺简单的:
- 想好逻辑控件要提供的功能。
- 思考这些功能需要为模板设计人员提供哪些接口,一般是:依赖属性、路由命令、PART_ 控件约定。(参考上面的 Template 设计。)
- 交互机制确定后,就可以编写相应的后台逻辑控制代码 以及 默认的控件样式(含模板)。
其它 Tips 及小技巧
- Blend 设计界面固然快,但是每次都需要编译、运行,要看一个效果往往需要多次调整。这时,我们可以使用 snoop 工具来直接调整运行时软件,当效果达到要求时,再把这些满意的值调整到 Blend 中。
- 一定要使用 Blend 而不是 VS 来设计界面,除非你对界面没有一点要求。
- 忘记“我用 VS 也能设计 WPF 界面”这种不切实际的想法吧。我个人就是因为之前有这种想法,导致一直对 WPF 不开窍。我认为这是一个学习 WPF 的误区,老是以开发人员的思维去思考 WPF。
- 学习 Blend 其实是很简单的一件事,相比 VS 的学习成本就简单太多了。如果你要是玩过 Flash、PS,玩起 Blend 来会更快。
- 虽然 Blend 说是给设计人员用的,但是我认为只有开发人员才能真正地用好 Blend,用好 WPF。
- 对于 XAML,不要象 C# 代码一样的追求代码重用。这种东西,Copy 一下改改就可以了。
- Theme 和 Resource:Theme 是主题文件,随着操作系统的主题变化。在开发自定义控件时会自动生成一个 Theme/Generic.xaml 文件。 可以在 Theme/ 这个文件夹中为不同的操作系统主题设计不同的控件样式,而找不到相关主题对应的文件时,则会使用 Generic.xaml 文件中的控件样式。所以:除了自定义控件的样式需要放到 Theme 中,当某个资源要随着系统主题变化而变化时,也需要把它编写到 Theme 文件夹中,否则,应该放到单独的资源文件中并收入到 Application 中。(这一点是个人的理解,不知道对不对,希望懂的大牛给指点下。)