在控件模板和为其提供支持的代码之间有一个隐含约定。如果使用自定义控件模板替代控件的标准模板,就需要确保新模板能够满足控件的实现代码的所有需要。
在简单控件中,这个过程就比较容易,因为对模板几乎没有(或者完全没有)什么真正的要求。对于复杂控件,问题就显得有些微妙了,因为控件的外观和实现不可能是完全相互独立的。对于这种情况,控件需要对其可视化显示做出一些假设,而不管曾经被设计得多么好。
在前面已经看到了控件模板的这种需求的两个例子,占位元素(如ContentPresenter和ItemsPresenter)和模板绑定。为成功地创建控件模板,需要仔细查看相关控件的标准模板,并注意分析这些技术的用法,然后将它们应用到自己的模板中。
嵌套的模板
按钮控件的模板可分解成几个较简单的部分。然而,许多模板并非如此简单。在某些情况下,控件模板将包含每个自定义模板也需要的大量元素。而在有些情况下,改变控件的外观涉及创建多个模板。
例如,假设计划修改熟悉的ListBox控件。创建这个示例的第一部是为ListBox控件设计模板,并酌情添加自动应用模板的样式。
该样式使用两个画刷绘制边框和背景。实际模板是标准模板ListBox的简化版本。在Border元素内部是为列表提供滚动功能的 ScrollViewer 元素以及容纳所有列表项的 ItemsPresenter 元素。
对于该模板,最值得注意之处是它未提供的功能——配置列表中各项的外观。没有改功能,被选择的元素总是使用熟悉的蓝色背景突出显示。为改变这种行为,需要为ListBoxItem控件添加控件模板,ListBoxItem 控件时封装列表中每个单独元素内容的内容控件。
与ListBox模板一样,可使用元素类型样式应用ListBoxItem模板。下面的基本模板在一个不可见的边框中封装了每个项。因为ListBoxItem是内容控件,所以需要使用ContentPresenter 元素在其内部放置项的内容。除这些基本内容外,还有当鼠标移动到项上或单击时做出响应的触发器:
总之,可以使用这两个模板创建当鼠标移动到当前定位的项上时使用动画放大项的列表框。因为每个ListBoxItem可具有自己的动画,所以当用户在列表框中上下移动鼠标时,将看到几个项开始增大,然后再次收缩。需要注意,缩小动画省略了From和To属性,通过这种方式,缩小动画总将文本从当前尺寸缩小到它原来的尺寸。
修改滚动条
列表框还有一个方面没有改变:右边的滚动条。它是ScrollViewer 元素的一部分,ScrollViewer元素是ListBox模板的一部分。尽管这里重新定义了ListBox模板,但没有替换ScrollBar的ScrollViewer。
为自定义该细节,可为ListBox控件创建一个新的ScrollViewer模板,然后可将 ScrollViewer模板指向自定义的ScrollBar模板。然而 ,还有更简单的选择。可创建一个改变所有ScrollBar控件模板的特性于元素类型的样式。这样就避免了创建ScrollViewer模板所需的额外工作。
当然,还需要考虑这种设计会对应用程序的其他部分造成什么影响。如果创建元素类型样式ScrollBar,并将其添加到窗口的Resources集合中,对于窗口的所有控件,无论何时使用ScrollBar控件,都会具有新样式的滚动条,这可能正是您所希望的效果。另一方面,如果希望只改变ListBox控件中的滚动条,就必须为ListBox控件本身的资源集合添加元素类型样式 ScrollBar。最后,如果希望改变整个应用程序中所有滚动条的外观,可将该样式添加到 App.xaml文件的资源集合中。
ScrollBar控件出奇复杂,它实际上是一个由更小部分组成的集合。
滚动条的背景有Track类表示——实际上是一个具有阴影并且被拉伸沾满整个滚动条长度的矩形。滚动条的末尾处是按钮,通过这些按钮可以向上或向下(或向左或向右)滚动一个步长。这些按钮时RepeatButton类的实例,该类继承自ButtonBase类。RepeatButton类和普通Button类之间的重要区别在于,如果在RepeatButton按钮上保持鼠标为按下状态,就会反复触发Click事件(对于滚动条这是非常方便的)。
在滚动条的中间是代表当前滚动内容中当前位置的Thumb元素。并且最有趣的是,滑块两侧的空白实际上由另外两个RepeatButton 对象构成,它们是透明的。当单击这两个按钮中的一个时,滚动条会滚动一整页(一页是滚动内容所在的可见窗口中的内容量)。通过单击滑块两侧的条形区域,可快速浏览滚动内容,这一功能是大家所熟悉的。
下面是用于垂直滚动条的模板:
一旦理解滚动条的多部分结构,上面的模板就非常直观了。下面列出需要注意的几个要点:
1、垂直滚动条由一个包含三行的网格构成。顶行和底行容纳两端的按钮(并显示为箭头)。它们固定占用18个单位。中间部分容纳Track元素,占用了剩余空间。
2、两端的RepeatButton元素使用相同的样式。唯一的区别是Content属性,该属性包含了一个用于绘制箭头的Path对象,因为顶部具有上箭头而底部的按钮具有下箭头。为简明起见,这些箭头使用微语言路径。其他细节(如背景填充和箭头周围显示的圆圈)是在控件模板中定义的。
3、两个按钮都链接到ScrollBar类的命令(LineUpCommand 和 LineDownCommand)。这正是其工作原理。只要提供链接到这个命令的按钮即可,不必考虑按钮的名称是什么,也不必考虑其外观像什么或使用哪个特定的类。
4、Track元素名为PART_Track。为使ScrollBar类能够成功的关联到它的代码,必须使用这个名称。如果查看ScrollBar类的默认模板(类似上面的模板,但更长一些),也会看到该元素。
5、Track.ViewportSize 属性被设置为0,。这是该模板特有的实现细节,可确保Thumb元素总有相同的尺寸(通常,滑块根据内容按比例地改变尺寸,因此如果滚动的内容在窗口中基本上能够显示,这是滑块会变得较长)。
6、Track元素封装了两个RepeatButton对象和Thumb元素。同样,这些按钮通过命令连接到适当的功能。
这里模板使用了键名,明确指定将它作为垂直滚动条,可确保它不能被自动应用,即使同时设置了TargetType属性。这里使用这种方法的原因是,该模板只适用于垂直方向的滚动条。而且,如果ScrollBar.Orientation属性设置为Vertical,元素类型样式会使用触发器自动应用控件模板:
尽管可以使用相同的基本部分很容易地创建水平滚动条,但这里没有采取该步骤,从而保留了正常样式的水平滚动条。
最后一项任务是填充格式化各个RepeatButton对象和Thumb元素的样式。这些样式比较简单,但它们确实改变了滚动条的标准外观。
与正常滚动条不同,在该模板中没有为Track元素指定背景,所以保持原来的透明背景。这样,列表框的轻微阴影渐变可透过滚动条显示。
至此,ListBox自定义模板就完成了,这里定义的ScrollBar样式会自动应用于ListBox模板中的ScrollViewer上。
代码清单:
Resources/Brush.xaml
Resources/ScrollBar.xaml
Resources/ListBox.xaml
MainWindow.xaml
One
Two
Three
Four
Five
Six
Seven
EightEight
NineNineNine
TenTenTenTen
ElevenElevenElevenEleven
TwelveTwelveTwelveTwelveTwelve
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestControlTemplate2;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}