对于喜欢将常用控件转变为非常用可视对象的程序员而言,Windows Presentation Foundation (WPF) 提供了一种令人兴奋不已的功能,即模板。控件的功能及其可视外观一向是由复杂的控件代码控制 。在 WPF 中,控件的功能仍通过代码实现,但视觉效果与该代码分离开来,并以 XAML 中定义的模板形 式存在。通过创建一个新模板(通常在 XAML 中,不用编写任何代码),程序员和设计师无需更改控件代 码就能彻底修改控件的可视外观。
在一年前的开篇专栏中,我讲述了如何为 ScrollBar、ProgressBar 和 Slider 控件设计模板。但模 板化功能有利有弊:在设计新的自定义控件时,您要为控件的可视外观提供一个默认模板,并允许该模板 由使用控件的程序员替换。您并不完全一定要这样构造控件——事实上,在拙作 “Applications = Code + Markup”(应用程序 = 代码 + 标记)(Microsoft Press, 2006) 中,没有任何自定义控件定义了可替换模板——但如果这么做的话,需要使用该控件的 人(包括您)会省事得多。
本专栏的目的不是为了创建功能完备、外观漂亮的控件,而是为了建 立一种机制,为分布在动态链接库中的控件定义默认可替换模板。我在此讨论的许多模板化技术都是通过 研究现有 WPF 控件上的模板学到的。如果您也想这么做,“Applications = Code + Markup”(应用程序 = 代码 + 标记)第 25 章中的 DumpControlTemplate 程序能让您以方便的 XAML 格式从所有标准 WPF 控件中提取默认模板。
元素和控件
体验过以前的 Windows 客 户端编程环境的程序员很快就会在 WPF 类层次结构中发现一个有趣的现象。例如,在本机 Windows API 中,任何具有屏幕上可视外观的东西都被归类为“窗口”,而在 Windows 窗体中,所有东西 都是“控件”。但在 WPF 中,Control 类和许多其他可视对象(尤其是 TextBlock、Image、 Decorator 和 Panel),都从 FrameworkElement 派生。那么,元素与控件到底有何区别呢?
首 先,Control 类将一组非常简单的属性添加到 FrameworkElement 类,包括 Foreground、Background 和 五个与字体相关的属性。Control 并不直接使用这些属性,它们只是为了方便从 Control 派生的类。
其次,Control 类添加了 IsTabStop 属性和 TabIndex 属性,这意味着控件在 tab 键导航链中 一般是停留点,而元素则不是。总而言之,元素用于观看,而控件则用于交互(但元素仍能获取焦点并对 键盘、鼠标和笔针输入作出响应)。
第三,Control 类定义 ControlTemplate 类型的 Template 属性。此模板一般是元素的可视树和构成控件可视外观的其他控件,通常还包含根据属性变化和事件而更 改此可视外观的触发器。
第三个特征意味着从 Control 派生的类有一个可自定义的可视外观,而 从 FrameworkElement 派生的其他类则没有。TextBlock 和 Image 当然都有可视外观,但自定义这些视 觉效果没有任何意义,因为这些元素不会给它们显示的格式化文本或位图增添任何东西。在另一方面, ScrollBar 可有多种外观,而功能则仍然相同。这就是模板的用途。
对于程序员来说,以下可能 是元素和控件之间最大的差别:如果从 FrameworkElement 派生,为了在屏幕上呈现元素的可视元素及其 子项,您很可能需要覆盖 MeasureOverride、ArrangeOverride 和 OnRender。如果从 Control 派生,通 常情况下并不需要覆盖这些方法,因为控件的视觉效果由 Template 属性的 ControlTemplate 对象中的 可视树定义。
WPF 包括一个名为 UserControl 的类,它通过 ContentControl 从 Control 派生 。通常推荐将此 UserControl 作为简单自定义控件的基类,其用途广泛。例如,拙作第 25 章中的 DatePicker 控件即从 UserControl 派生。但请记住 Control 与 UserControl 之间的如下显著区别:当 从 UserControl 派生时,您可以在 XAML 中定义可视树,但此可视树是 UserControl 的 Content 属性 的子项。UserControl 自有其简单的默认模板,您可能不会替换该模板,因为它将 ContentPresenter 嵌 套在 Border 内部。
从 UserControl 所派生类的可视树并不是用来被替换的,因此该类的代码及其可视树可以更紧密地耦 合。相反,如果您打算从 Control 派生并提供一个可替换的默认模板,代码和可视树之间的交互则应该 既简单又记录完备。