在控件模板和为其提供支持的代码之间又一个隐含约定。若是使用自定义控件模板替代控件的标准模板,就须要确保新模板可以知足控件的实现代码的全部须要。ide
在简单控件中,这个过程比较容易,由于对模板几乎没有(或彻底没有)什么真正的需求。对于复杂控件,问题就显得有些微妙了,由于控件的外观和实现不可能彻底相互独立的。对于这种状况,控件须要对其可视化显示作出一些假设,而无论曾经被设计的多好。动画
在前面已经看到了控件模板的这种需求的两个例子,占位元素(如ContentPresenter和ItemsPresenter)和模板绑定。接下来的将例举另外两个例子:具备特定名称(以PART_开头)的元素和专门设计的用于控件模板的元素(如ScrollBar控件中的Track元素)。为成功地建立控件模板,须要仔细查看相关控件的标准模板,并注意分析这4种技术的用法,而后将他们复制到本身的模板中。spa
1、嵌套的模板设计
按钮控件的模板可分解成几个较简单的部分。然而,许多模板并不是如此简单。在某些状况下,控件模板将包含每一个自定义模板也须要的大量元素。而在有些状况下,改变控件的外观涉及建立多个模板。3d
例如,假设计划修改熟悉的ListBox控件。建立这个示例的第一步是为ListBox控件设计模板,并酌情添加自动应用模板的样式。下面的标记将这两个要素合并到一块儿:code
<Style TargetType="{x:Type ListBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}"> <Border Name="Border" Background="{StaticResource ListBoxBackgroundBrush}" BorderBrush="{StaticResource StandardBorderBrush}" BorderThickness="1" CornerRadius="3" > <ScrollViewer Focusable="False"> <ItemsPresenter Margin="2"></ItemsPresenter> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
该样式使用两个画刷绘制边框和背景。实际模板是标准模板ListBox的简化版本,但没有使用ListBoxChrome类,而使用了较简单的Border元素。在Border元素内部是为列表提供滚动功能的ScrollViewer元素以及容纳全部列表项的ItemsPresenter元素。对象
对于该模板,最值的注意之处是它未提供的功能——配置列表中各项的外观。没有该功能,呗选择的元素老是使用熟悉的蓝色背景突出显示。为改变这种行为,须要为ListBoxItem控件添加控件模板,ListBoxItem控件是封装列表中每一个单独元素内容的内容控件。blog
与ListBox模板同样,可以使用元素类型样式应用ListBoxItem模板。下面的基本模板在一个不可见的边框中封装了每一个项。所以ListBoxItem是内容控件,因此须要使用ContentPresenter的触发器:继承
<Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border Name="Border" BorderThickness="2" CornerRadius="3" Padding="1"> <ContentPresenter/> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="ListBoxItem.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="FontSize" To="20" Duration="0:0:1"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="ListBoxItem.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="FontSize" BeginTime="0:0:0.5" Duration="0:0:0.2"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource HoverBorderBrush}"></Setter> </Trigger> <Trigger Property="IsSelected" Value="True"> <Setter TargetName="Border" Property="Background" Value="{StaticResource SelectedBackgroundBrush}"/> <Setter TargetName="Border" Property="TextBlock.Foreground" Value="{StaticResource SelectedForegroundBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
总之,可使用这两个模板建立当将鼠标移动到当前定位的项上时使用动画放大项的列表框。由于每一个ListBoxItem可具备本身的动画,因此当用户在列表框中上下移动鼠标时,将看到几个项开始增大,而后再次收缩,建立了动人的“鱼眼”效果(当将鼠标悬停在项上时,使用具备动画的变换,可实现更夸张的鱼眼效果,放大项并使项变形)。事件
尽管不可能在一幅图像中捕获这种效果,下图显示了将鼠标快速移过几个项以后该列表的快照。
在此不会从新分析整个ListBoxItem模板示例,由于它由许多不一样部分构建,包括用于设置ListBox控件、ListBoxItem控件以及ListBox控件的各类组成元素(如滚动条)样式的部分,其中重要的部分是改变ListBoxItem模板的样式。
在这个示例中,ListBoxItem对象较缓慢地扩大(通过1秒),而后更快地进行缩小(通过0.2s)。然而,在开始缩小动画以前有0.5秒得延迟。
须要注意,缩小动画省略了From和To属性。经过这种方式,缩小动画总将文本从当前尺寸缩小到它原来的尺寸。若是将鼠标移到ListBoxItem上而后移开,就会获得所指望的效果——当鼠标停留在项上时,项会不断地扩张,当移走鼠标时项会不断地缩小。
2、修改滚动条
列表框还有一个方向没有改变:右边的滚动条。它是ScrollViewer元素的一部分,ScrollViewer元素是ListBox模板的一部分。尽管该例从新定义了ListBox模板,但没有替换ScrollBar的ScrollViewer。
为自定义该细节,可为ListBox控件建立一个新的ScrollViewer模板。而后可将ScrollViewer模板指向自定义的ScrollBar模板。然而,还有更简单的选择。可建立一个改变全部ScrollBar控件模板的特定于元素类型的样式。这样就避免了建立ScrollViewer模板所需的额外的工做。
固然,还须要考虑这种设计会对应用程序的其余部分形成什么影响。若是建立元素类型样式ScrollBar,并将其添加到窗口的Resources集合中,对于窗口中的全部控件,不管什么时候使用ScrollBar控件,都会有新样式的滚动条,这可能正是你所但愿的效果。另外一方面,若是但愿只改变ListBox控件中的滚动条,就必须为ListBox控件自己的资源集合添加元素类型样式ScrollBar。
滚动条的背景由Track类表示——实际上时一个具备阴影而且被拉伸占满整个滚动条长度的矩形。滚动条的末尾处是按钮,经过这些按钮能够向上或向下(或向左或向右)滚动一个步长。这些按钮是RepeatButton类的实例,该类继承自ButtonBase类。RepeatButton类和广泛Button类之间的重要区别在于,若是在RepeatButton按钮上保持鼠标为按下状态,就会反复触发Click事件(对于滚动条这是很是方便的)。
在滚动条的中间是表明滚动内容中当前位置的Thumb元素。而且最有趣的是,滑块两侧的空白实际上由另外两个RepeatButton对象构成,它们都是透明的。当单击这两个按钮中的一个时,滚动条会滚动一整页(一页是滚动内容所在的可见窗口中的内部容量)。经过单击滑块两侧的条形区域,可快速浏览滚动内容,这一功能是你们所熟悉的。
下面是用于垂直滚动条的模板:
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}"> <Grid> <Grid.RowDefinitions> <RowDefinition MaxHeight="18"/> <RowDefinition Height="*"/> <RowDefinition MaxHeight="18"/> </Grid.RowDefinitions> <RepeatButton Grid.Row="0" Height="18" Style="{StaticResource ScrollBarLineButtonStyle}" Command="ScrollBar.LineUpCommand" > <Path Fill="{StaticResource GlyphBrush}" Data="M 0 4 L 8 4 L 4 0 Z"></Path> </RepeatButton> <Track Name="PART_Track" Grid.Row="1" IsDirectionReversed="True" ViewportSize="0"> <Track.DecreaseRepeatButton> <RepeatButton Command="ScrollBar.PageUpCommand" Style="{StaticResource ScrollBarPageButtonStyle}"> </RepeatButton> </Track.DecreaseRepeatButton> <Track.Thumb> <Thumb Style="{StaticResource ScrollBarThumbStyle}"> </Thumb> </Track.Thumb> <Track.IncreaseRepeatButton> <RepeatButton Command="ScrollBar.PageDownCommand" Style="{StaticResource ScrollBarPageButtonStyle}"> </RepeatButton> </Track.IncreaseRepeatButton> </Track> <RepeatButton Grid.Row="3" Height="18" Style="{StaticResource ScrollBarLineButtonStyle}" Command="ScrollBar.LineDownCommand"> <Path Fill="{StaticResource GlyphBrush}" Data="M 0 0 L 4 4 L 8 0 Z"></Path> </RepeatButton> </Grid> </ControlTemplate>
一旦理解滚动条的多部分结构,上面的模板就很是直观了。下面列出须要注意的几个要点:
垂直滚动条由一个包含三行的网格构成。顶行和底行容纳两端的按钮(并显示为箭头),它们固定占用18个单位。中间部分容纳Track元素,占用剩余空间。
两端的RepeatButton元素使用相同的样式。惟一的区别是Content属性,该属性包含了一个用于绘制箭头的Path对象,由于顶部的按钮具备上箭头而底部的按钮具备下箭头。
两个按钮都链接到ScrollBar类中的命令(LineUpCommand和LineDownCommand)。这正是其工做原理。只要提供连接到这个命令的按钮便可,没必要考虑按钮的名称是什么,也没必要考虑其余外观像什么或使用哪一个特定的类。
Track元素名为PART_Track。为使ScrollBar类可以成功地关联到它的代码,必须使用这个名称。若是查看ScrollBar类的默认模板(相似与上面的模板,但更长一些),也会看到该元素。
Track.ViewportSize属性被设置为0,。这是该模板特有的实现细节,可确保Thumb元素总有相同的尺寸(一般,滑块根据内容按比例地改变尺寸,所以若是滚动的内容在窗口中基本上可以显示,这是滑块会变得较长)。
Track元素封装了两个RepeatButton对象(它们的样式单独定义)和Thumb元素。一样,这些按钮经过命令链接到适当的功能。
经过上面代码,发现模板使用了键名,明确指定它做为垂直滚动条。当为样式设置键名时,可确保它不能被自动应用,即便同时设置了TargetType属性也是如此。该例使用这种方法的缘由是,该模板只适用于垂直方向的滚动条,并且若是ScrollBar.Orientation属性被设置为Vertical,元素类型样式会使用触发器自动应用控件模板:
<Style TargetType="{x:Type ScrollBar}"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <Style.Triggers> <Trigger Property="Orientation" Value="Vertical"> <Setter Property="Width" Value="18"/> <Setter Property="Height" Value="Auto" /> <Setter Property="Template" Value="{StaticResource VerticalScrollBar}" /> </Trigger> </Style.Triggers> </Style>
尽管可使用相同的基本部分很容易地建立水平滚动条,但该例没有采用该步骤(从而保留了正常样式的水平滚动条)。
最后一项任务是填充格式化各个RepeatButton对象和Thumb元素的样式。这些样式比较简单,但它们确实改变了滚动条的标准外观。首先,Thumb元素的形状被设置成相似椭圆的形状:
<Style x:Key="ScrollBarThumbStyle" TargetType="{x:Type Thumb}"> <Setter Property="IsTabStop" Value="False"/> <Setter Property="Focusable" Value="False"/> <Setter Property="Margin" Value="1,0,1,0" /> <Setter Property="Background" Value="{StaticResource StandardBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource StandardBorderBrush}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Thumb}"> <Ellipse Stroke="{StaticResource StandardBorderBrush}" Fill="{StaticResource StandardBrush}"></Ellipse> </ControlTemplate> </Setter.Value> </Setter> </Style>
接下来,在美观的圆圈中绘制两端的箭头。这些圆圈是在控件模板中定义的,而箭头由RepeatButton对象的内容提供,并使用ContentPresenter元素插入到控件模板中:
<Style x:Key="ScrollBarLineButtonStyle" TargetType="{x:Type RepeatButton}"> <Setter Property="Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type RepeatButton}"> <Grid Margin="1"> <Ellipse Name="Border" StrokeThickness="1" Stroke="{StaticResource StandardBorderBrush}" Fill="{StaticResource StandardBrush}"></Ellipse> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsPressed" Value="true"> <Setter TargetName="Border" Property="Fill" Value="{StaticResource PressedBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
显示在Track元素上面的RepeatButton对象没有发生改变。它们只使用透明背景,使Track元素可透过它们显示:
<Style x:Key="ScrollBarPageButtonStyle" TargetType="{x:Type RepeatButton}"> <Setter Property="IsTabStop" Value="False"/> <Setter Property="Focusable" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type RepeatButton}"> <Border Background="Transparent" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
与正常的滚动不一样,在该模板中没有为Track元素指定背景,因此保持原有的透明背景。这样,列表框的轻微阴影渐变可透过滚动条显示。下图是最终的列表框: