在控件模板和为其提供支持的代码之间又一个隐含约定。如果使用自定义控件模板替代控件的标准模板,就需要确保新模板能够满足控件的实现代码的所有需要。

  在简单控件中,这个过程比较容易,因为对模板几乎没有(或完全没有)什么真正的需求。对于复杂控件,问题就显得有些微妙了,因为控件的外观和实现不可能完全相互独立的。对于这种情况,控件需要对其可视化显示做出一些假设,而不管曾经被设计的多好。

  在前面已经看到了控件模板的这种需求的两个例子,占位元素(如ContentPresenter和ItemsPresenter)和模板绑定。接下来的将例举另外两个例子:具有特定名称(以PART_开头)的元素和专门设计的用于控件模板的元素(如ScrollBar控件中的Track元素)。为成功地创建控件模板,需要仔细查看相关控件的标准模板,并注意分析这4种技术的用法,然后将他们复制到自己的模板中。

一、嵌套的模板

  按钮控件的模板可分解成几个较简单的部分。然而,许多模板并非如此简单。在某些情况下,控件模板将包含每个自定义模板也需要的大量元素。而在有些情况下,改变控件的外观涉及创建多个模板。

  例如,假设计划修改熟悉的ListBox控件。创建这个示例的第一步是为ListBox控件设计模板,并酌情添加自动应用模板的样式。下面的标记将这两个要素合并到一起:

<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控件是封装列表中每个单独元素内容的内容控件。

  与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上然后移开,就会得到所期望的效果——当鼠标停留在项上时,项会不断地扩张,当移走鼠标时项会不断地缩小。

二、修改滚动条

  列表框还有一个方向没有改变:右边的滚动条。它是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元素指定背景,所以保持原有的透明背景。这样,列表框的轻微阴影渐变可透过滚动条显示。下图是最终的列表框:

【WPF学习】第六十二章 构建更复杂的模板的更多相关文章

  1. 【WPF学习】第二十二章 文本控件

    WPF提供了三个用于输入文本的控件:TextBox.RichTextBox和PasswordBox.PasswordBox控件直接继承自Control类.TextBox和RichTextBox控件间接 ...

  2. “全栈2019”Java第六十二章:接口与常量详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  3. o'Reill的SVG精髓(第二版)学习笔记——第十二章

    第十二章 SVG动画 12.1动画基础 SVG的动画特性基于万维网联盟的“同步多媒体集成语言”(SMIL)规范(http://www.w3.org/TR/SMIL3). 在这个动画系统中,我们可以指定 ...

  4. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  5. 【WPF学习】第十四章 事件路由

    由上一章可知,WPF中的许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容.例如,可构建包含图形的按钮,创建混合了文本和图片内容的标签,或者为了实现滚动或折叠的显示效果而在特定容器中放置 ...

  6. 【WPF学习】第六十四章 构建基本的用户控件

    创建一个简单用户控件是开始自定义控件的好方法.本章主要介绍创建一个基本的颜色拾取器.接下来分析如何将这个控件分解成功能更强大的基于模板的控件. 创建基本的颜色拾取器很容易.然而,创建自定义颜色拾取器仍 ...

  7. 【WPF学习】第二十九章 元素绑定——将元素绑定到一起

    数据banding的最简单情形是,源对象时WPF元素而且源属性是依赖性属性.前面章节解释过,依赖项属性具有内置的更改通知支持.因此,当在源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性.这 ...

  8. 【WPF学习】第十五章 WPF事件

    前两章学习了WPF事件的工作原理,现在分析一下在代码中可以处理的各类事件.尽管每个元素都提供了许多事件,但最重要的事件通常包括以下5类: 生命周期事件:在元素被初始化.加载或卸载时发生这些事件. 鼠标 ...

  9. 《机器学习实战》学习笔记第十二章 —— FP-growth算法

    主要内容: 一.  FP-growth算法简介 二.构建FP树 三.从一颗FP树中挖掘频繁项集 一.  FP-growth算法简介 1.上次提到可以用Apriori算法来提取频繁项集,但是Aprior ...

随机推荐

  1. TensorFlow入门知识

    Tensorflow基本操作 Tensorflow是一种计算图模型,即用图的形式来表示运算过程的一种模型.Tensorflow程序一般分为图的构建和图的执行两个阶段.图的构建阶段也称为图的定义阶段,该 ...

  2. Jessica's Reading Problem POJ - 3320

    Jessica's Reading Problem Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 17562   Accep ...

  3. OpenGL 实践之贝塞尔曲线绘制

    说到贝塞尔曲线,大家肯定都不陌生,网上有很多关于介绍和理解贝塞尔曲线的优秀文章和动态图. 以下两个是比较经典的动图了. 二阶贝塞尔曲线: 三阶贝塞尔曲线: 由于在工作中经常要和贝塞尔曲线打交道,所以简 ...

  4. 用python实现LBP特征点计算

    import cv2 import numpy as np def olbp(src): dst = np.zeros(src.shape,dtype=src.dtype) for i in rang ...

  5. VsCode从零开始配置一个属于自己的Vue开发环境

    vscode vue VsCode算是比较热门的一个代码编辑器了,全名Visual Studio Code下载地址:点我去下载插件众多,功能齐全,我在平常开发过程中都是用的它,整理了些自认好用的插件, ...

  6. BrowserSync(保存代码后,自动刷新浏览器)

    摘要 Browsersync能让浏览器实时.快速响应您的文件更改(html.js.css.sass.less等)并自动刷新页面.更重要的是 Browsersync可以同时在PC.平板.手机等设备下进项 ...

  7. HTML5 history-hash 随机选择彩票

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  8. 创建和存储 cookie

    在这个例子中我们要创建一个存储访问者名字的 cookie.当访问者首次访问网站时,他们会被要求填写姓名.名字会存储于 cookie 中.当访问者再次访问网站时,他们就会收到欢迎词. 首先,我们会创建一 ...

  9. asp:textbox 的 TextMode:password

    1.  用于输入或显示密码的文本框,设置属性TextMode为Password <asp:TextBox ID="txt_Password" runat="serv ...

  10. jvm 性能调优工具之 jps 命令详解

    JPS名称:jps - Java Virtual Machine Process Status Tool命令用法:jps [options] [hostid] options:命令选项,用来对输出格式 ...