《Programming WPF》翻译 第5章 样式和控件模板

总而言之,对文档的处理,“样式”是应用到某个范围内容的一组属性,如文本、图像等等。举例来说,我们正在使用的样式名为“Normail,Body,b”,并用于预出版的文档,这意味着使用10Times字体,完全对齐。文档的稍后部分,我们将要使用名为“Code,x,s”的样式,这将使用9Courier New字体,居左对齐。当内容生成内容的时候,样式就应用到内容,产生确定的外观。

WPF中,样式同样是一组应用到内容的属性,用于生成可视化。样式能在已有可视化元素上设置属性,正如设置Button控件的字体宽度;或可以用于定义对象外观的方式,正如显示Person对象的nameage。除了word处理样式的特色之外,WPF样式有一些特殊的特征,用于生成应用程序,包括了基于用户事件的联合不同的可视化效果的能力,为已有控件提供了完整的新外观,甚至为非可视化对象指明了行为。所有这些特征不再伴随着创建一个自定义控件的需要(虽然这只是一件能够做到的有用的事,如。第9章所讨论的)。

5.1不使用样式

作为一个样式如何使其在WPF使用的例子,,让我们看一下TTT简单的实现,如示例5-1

示例5-1

这个grid的外观上排列了一组9个按钮在一个3X3栅格的TTT单元中,在按钮上使用了页面空白为了TTT的交叉线阴影。对游戏逻辑的一个简单的实现,在xaml后台代码中,如示例5-2所示。

示例5-2

我们的简单TTT逻辑使用字符串代表玩家,使用按钮来跟踪游戏状态。当点击任意一个按钮时,我们将内容设置为字符串,用来象征当前玩家以及转换玩家。当游戏结束的时候,每一个按钮上的内容都会被清除。游戏中的截图如图5-1

5-1

注意到图5-1中,grid的背景来自页面的空白。这些空白差不多使grid看上去像一个可绘制的TTT木板(虽然我们将来会做的更好)。然而,如果我们真的指望模仿一个手绘的游戏,我们已经对按钮上的字体大小做了设置,但并没匹配到线条的厚度。

一种修复这个问题的方法是为每一个按钮对象设置字体和宽度,如示例5-3

示例5-3

依照我的视觉敏感性,今天,虽然这样做使得X的和O的外观更好,一旦我以后想改动它,我就要负责在9个独立的地方改变这些属性,这是重复性的努力——违反了我的编码敏感性。我宁愿重制我的决定——为了以后的维护,将我的TTT单元的外观放在一个共同的地方。这是样式派得上用场的地方。

5.2内嵌样式

每一个“可样式化”的WPF元素都有一个Style属性,可以在内部设置这个属性——使用XAML属性-元素的语法(在第一章讨论的),如示例5-4

示例5-4

因为我们想捆绑属性值在我们的样式中,我们有一个带有两个Setter子元素的Style元素,一个是我们想要设置的每个属性,如FontSizeFontWeight,都带有Button前缀,指出了包含属性的类。适用于样式的属性是依赖属性,这将在第九章介绍。

由于额外的样式语法,且因为内嵌样式不能跨元素共享,所以内嵌样式事实上陷入了设置属性时更多的输入。由于这个原因,内嵌样式并没有命名样式经常使用。

5.3命名属性

通过把同样的内嵌样式提升到资源中(正如第一章介绍的),我们可以给它一个名字,以及按名字使用它在我们的Button实例上,正如示例5-5

示例5-5

在示例5-5中,我们在属性中使用到Control前缀取代Button前缀,从而允许样式更广泛的应用,正如我们将要看到的。

5.3.1 TargetType属性

方便起见,如果所有的属性可以在一个共享类中设置,像我们示例中的Control,我们可以提升这个类的前缀,放入TargetType属性中,并将其从属性的名称中移除掉。如示例5-6所示。

示例5-6

当提供了一个TargetType属性,你能够只设置该类型可接受的属性。如果你想要在继承树下扩展一组大量的属性,你可以通过使用一个派生类型来做到这些,如示例5-7

示例5-7

在这种情形中,IsCancel属性只在Button上有效,因此,为了设置它,我们需要为了这个类型转换TargetType属性。

#你可能想知道为什么我设置FontSize为“32”而不是“32pt”,当详细的指定了字体大小时,后者在一条直线上占得更多,。这两种表示法明显是不相等的。(前者是pixels,而后者是points)。由于这种写法,我使用pixelsWPF样式使用了一个不加前缀的属性,允许“32pt”明确指定为FontSize,然而前缀属性并不是这样。例如,下面的代码能正常运行(假设Target已经被设置了):

<Setter Property=”FontSize” Value=”32pt” />

而以下并没有这么做(不管TargetType是否被设定)

<Setter Property=”Control.FontSize” Value=”32pt” />

希望在你读到这里的时候,这个问题已经被修复了。(而且没有被其它所取代)

5.3.2样式复用

除了将你从要为每一个属性名输出类前缀的名称解救出来之外,TargetType属性还会检查所有应用了样式的类是那个类型或派生的类型的一个实例。这意味着一旦我们将TargetTypeControl中设置,我们就能将其应用到一个Button元素,而不是一个TextBlock元素,前者最终派生于Control,而后者不是。

另一方面,尽管ControlTextBlock都共享共同的FrameworkElement祖先,FrameworkElement并没有定义一个FontSize依赖属性,因此一个带有FrameworkElement.TargetType的样式不会让我们设置FontSize属性,因为它并不在那里,尽管事实上ControlTextBlock都有一个FontSize属性。

即使在Control中设置TargetType,我们获得了一个跨越类来复用样式的尺度,这些类是派生于Control的,如ButtonLabelWindow等等。尽管如此,如果我们从样式中一起去除TargetType属性,我们获得了一个跨越控件来复用样式的尺度,不再有一个公有的基础,而是共享一个依赖属性的实现。根据我的经验,我发现依赖属性跨越类共享了同样的名称,如Control.FontSizeTextBlock.FontSize,还共享了一个实现。这意味着即使ControlTextBlock各自定义了它们的FontSize属性,在运行时它们共享这个属性的实现,所以我可以编写示例5-8那样的代码。

示例5-8

在示例5-8中,我从样式定义中去除了TargetType属性,使用了替代的雷前缀在每个样式设置的属性上。这个样式能被应用到一个Button元素,正如你期望的,也能应用到一个TextBlock控件上,随着FontSize在样式中明确地设定。这样工作的原因是ButtonControl获取它的FontSize依赖属性定义,而TextBlock,提供它的FontSize依赖属性定义,共享由TextElement实现的FontSize依赖属性。图5-2显示了元素间的关系及他们的依赖属性实现。

如图5-2所示,如果我们需要,我们可以按照TextElement重新定义我们的样式,即使它陷入机成熟,既不是Control也不是TextBlock,如示例5-9所示。

5-2

示例5-9

更进一步,如果我们想要定义一个包含属性的样式,这些属性没有被每个我们要应用样式的元素共享,我们也可以这么做,如示例5-10

示例5-10

在示例5-10中,我们已经添加了Button.IsCancel属性在CellTextStyle中,以及将其应用到有这个属性的Button元素,而TextBlock元素则没有这个属性。那好,在运行时,WPF将应用依赖属性到拥有这个属性的元素上

#WPF的应用样式到对象的能力,并不要求对象有所有的属性定义在样式中,类似的应用到WordNormal样式,包含一个字体类型属性,同时包括一段文本和一个图片。即使Word知道图片没有字体类型,它仍然应用到Normal样式的一部分使之确实有意义。(正如对齐属性),忽略其余的内容。

跨越不同控件的样式复用给我的应用程序一致的外观,如图5-3所示。

你应该注意的是图5-3的状态文字,尽管按钮中的文字是黑色的。由于黑色是默认的文字颜色,如果我们想要状态文字的显示相对于黑色背景,我们不得不将颜色改变为其它,因此需要在TextBlock上设置Foreground属性为白色。联合样式,每一个示例属性Setting都工作正常,而且,你可以联合两种设置属性值的技术正如你看到的。

5-3

5.3.3

进一步,如果我们想要在一个指定的实例上复写一个样式属性,通过在实例上设置属性,我们是可以做到的,正如示例5-12

示例5-12

在示例5-12中,在TextBlock实例属性上设置FontWeight,优先于在样式属性上设置FontWeight

5.3.4继承样式属性

为了完成oo的三个特征:复用,复写,继承,你可以从一个基础样式继承一个样式,添加新属性或者重写已有属性,如示例5-13所示。

示例5-13

BaseOn样式属性用于指定一个基础样式。在示例5-3中,StatusTextStyle样式继承了所有CellTextStyle属性的设置,复写了FontWeight,以及ForegroundHorizontalAlignment的设置。注意到,HorizontalAlignment属性使用了TextBlock前缀,这是因为TextElement没有HorizontalAlignment这个属性。

我们当前使用的样式了引起了TTT游戏看上去如图5-4

5-4

到目前为止,我们的应用程序相当不错,尤其是状态文字上的细字体宽度,但是我们可以做得更好。

5.3.5编程上设置样式

一旦一个样式有了名字,从我们的代码中是很好利用的。例如,我们可能决定要每个玩家有自己的样式。在这种情形中,在xaml中,在编译期使用命名属性不会成功,因为我们想基于内容设置样式,而内容是直到运行期才知道的。然而,没有什么要求我们去设置一个控件静态的Style属性;我们也可以编程实现,如示例5-14

示例5-14

在示例5-14中,无论玩家何时点击,除了设置按钮内容之外,我们从窗体资源中拉出一个命名样式并将其设置给按钮的样式。这假设了一对命名样式在窗体级别被定义,如示例5-15

示例5-15

随着这些适当位置的样式,以及伴随着内容的设置按钮样式的代码,我们得到图5-5

注意到,所有的XO都根据命名玩家的样式设置了颜色。在这种特定的情形中(以及其他情形),数据触发器(在5.6中讨论)应该被优先选择用来以编程方式设置样式,但是你不知道什么时候将要必须去堵塞。

#作为所有xaml的构造器,你可以自由地以编程方式创建样式。附录A是一个好的介绍——关于如何考虑在xaml和代码之间来来往往的进行。

5-5

5.4元素类型样式

命名样式非常有用,当你得到一组属性并应用到特点的元素上。然而,如果你想要应用一个统一的样式到所有确定元素类型的实例,设置TargetType而不用一个Key,如示例5-16所示。

示例5-16

在示例5-16所示,我们已经得到了两种样式,一种是带有TargetTypeButton,没有key;另一种是带有TargetTypeTextBlock,没有key。它们都以同样的方式工作;当创建一个ButtonTextBlock的实例而不用现实地设置Style属性,它使用的样式将目标类型匹配到控件的类型。我们的元素类型样式返回了我们的游戏如图5-4所示。

元素类型样式是便利的,无论何时你想要所有特定元素的实例共享一个外观,依赖于范围。例如,迄今,在顶级窗体中,我们已经在示例中为样式设置了范围,如示例5-17

示例5-17

尽管如此,我们可能想缩小元素类型样式的范围。在我们的示例中,这将工作良好将样式限定范围在grid中,从而只有grid中的ButtonTextBlock受到影响,如示例5-18

示例5-18

或者,如果你想使你的样式在你的工程中有更大的作用区域,你可以将它们放在应用程序范围内,如示例5-19

示例5-19

一般而言,理解元素类型的样式范围规则是有用的,因此你可以判断它们在各种WPF对象模型中的效果。第6章更加详细地讨论了所有种类的资源范围,包括样式。

#命名样式和元素类型样式

当对命名样式还是元素类型样式使用作出选择时,我们的一位评论家说,按照他的经验,一旦你有10个以上给予元素类型的样式,对一个特定的控件获取它的样式保持跟踪将非常困难。这是一个原因是我成为命名样式的粉丝。

对于我而言,样式是一个在一个地方应用到内容的语义标签,并且在另一个地方也能获得一个可视化表示。正如我们的TTT示例那样简单,我们已经得到了两个样式,一个是为了状态文字,另一个是为了移动的单元;在我们这么做之前,我们将要得到更多。主要的区别因素是我们在这些元素中显示的数据种类,而不是保持数据的元素类型。实际上,我们有一些分配到TextBox控件的样式,这将无论如何打消基于类型的样式,甚至是这个简单的应用程序。

5.5数据模板和样式

让我们想象一下我们想要实现TTT更有娱乐性的一个版本(这是大部分游戏中最重要的特色)。例如,TTT的一种变体允许玩家每次只能占据3个格子,去除第一步在下第四步的时候,去除第二步在下第五步的时候,,以此类推。为了实现这个变体,我们需要保持对每一步按顺序跟踪——可以利用PlayMover类,如示例5-20

示例5-20

现在,取代以为每个按钮对象的内容使用一个简单的字符串,我们将用示例5-20中的一个PlayMover实例鲜明显示这样的一个改动。

示例5-21

5-6

正如你回想到的,在第四章,图5-6所发生的是,按钮没有足够多的信息生成一个PlayMover对象,但是我们可以通过一个数据模板修复它。

5.5.1数据模板

回忆第四章,WPF允许你定义一个数据模板,这是一棵元素树,可以在特定的上下文中扩展。数据模板用于提供一个应用程序生成非可视化外观对象的能力,正如示例5-22所示。

示例5-22

使用在第一章介绍的xaml映射语法,我们将PlayMover类型间接映射到了带有xmlns属性的xaml中,我们将其作为数据模板的数据类型。现在,无论何时WPF遇到一个PlayMove对象,如我们所有的按钮内容,数据模板都会展开。在我们这种情形,这个模板由一个grid组成,其中排列了两个TextBlock,一个在按钮中间显示玩家名,另一个在按钮右下位置显示移动的步数,伴随着其它的一些设置使之看起来更漂亮。

5.5.2带样式的数据绑定

尽管如此,这些属性设置深埋在数据模板的很深层。正像这是一个好的想法将“魔力数字”移出你的代码,将它们拉出去并给它们一个易于管理的名称,这是一个好的想法将成组的设置移动到样式中,如示例5-23所示。

*将成组的设置移动到样式中,还顾及皮肤和主题,在第六章会介绍。

示例5-23

使用样式是很通常的,从而可以使用数据模板设置成组的属性,创建带有大量属性的大量元素。图5-7显示了这个结果。

5-7

仍然,像图5-7一样漂亮,交互作用是一种WPF给予的令人厌烦的能力。让我们看一下使用在应用程序中使用样式属性我们都能做些什么。

5.6触发器

目前为止,我们已经看到样式,作为一个Setter元素的集合。当应用一个样式时,在Setter元素中描述的设置不会无条件地应用(除非复写每一个设置的实例)。另一方面,触发器是一种在条件中包装了一个或更多Setter元素的方式,如果条件为真,相应地Setter元素会被执行,而条件为false的时候,属性值返回预先触发的值。

WPF伴随着3种你可以在一个触发器条件中检查的事情一起发生,依赖属性,.NET属性,.NET事件。头两个直接改变基于条件的值,如我所描述的;而最后一个,一个事件触发器,被激活于一个事件发生并开始一个引起属性变化的动画。

5.6.1属性触发器

触发器最简单的形式是属性触发器,它将监视一个依赖属性得到一个确定的值。例如,如果我们想要使一个按钮当用户移动鼠标到这个按钮上的时候变为黄色,我们可以这么做通过注释IsMouseOver属性是否有一个true值,正如示例524所示。

示例5-24

一些触发器在Style.Triggers元素下被分组。在这种情形中,我们添加了一个Trigger元素到button的样式中。当isMouseOver属性为true时,这个按钮的Background值会被设为黄色,正如图58所示。

在图5-8中,你会注意到,只有鼠标当前所在位置的按钮会被设置黄色背景,即使其他的按钮曾经在鼠标下也没有变色。当触发器不再为真时,没有必要着急于使这个属性回到原先,例如,监视IsMouseOverfalseWPF依赖属性系统监视到属性触发器变为无效的时,就会回复到原先设置的值。

5-8

可以设置属性触发器来监视控件上的任何一个依赖属性,到样式的定位,以及设置控件上的任何依赖属性到当条件为true的时候。实际上,你可以使用一个单独的触发器来设置你喜欢的多重属性,如示例5-25

示例5-25

在示例5-25中,我们设置背景为黄色和字体样式为italic,当鼠标在按钮上的时候。

#在触发器中不允许设置Style属性自身。如果你尝试这么做,会的到下面的错误:

A Style object is not allowed to affect the Style property of the object to which it applies.

这样做是有意义的。由于是Style在设置属性,而且当触发器不再为true时,还有参与将这个属性改为“未设置”,

这看上去有点像转换你的幽默故事在你   在你着陆之前在开始奋力一跳之后。

5.6.2多重触发器

当你可以设置任意多的属性在你的属性触发器的时候,在一个样式中可能存在多于一个的触发器。当对Style.Trigger下的元素进行分组的时候,多重触发器表现为每个触发器各自独立。

例如,我们可以更新代码为了鼠标移动到我们的一个按钮之上,这个按钮将被设为黄色,而且一旦该按钮获得焦点(tab或箭头移动到焦点范围内),这个按钮会被设为绿色,如示例5-26所示。图59显示了这样的结果:一个单元格得到了焦点,另一个则有鼠标盘旋。

示例5-26

5-9

入如果多重触发器设置了相同的属性,会采取最后的一个。例如,在图5-9中,如果一个按钮得到焦点,同时鼠标盘旋其上,它的背景会变为绿色,因为IsFocused触发器是触发器列表的最后一个。

5.6.3多重条件的属性触发器

如果你想要检查多于一个的属性在一个触发器条件激活前,例如,鼠标盘旋在一个按钮上,按钮内容为空。你可以联合这些条件在一个多重条件属性触发器中,如示例5-27所示。

示例5-27

多重条件属性触发器检查所有属性的值是否被明确指定,而不仅是其中的一个。这里,我们监视按钮盘旋以及内容不为空*,反映了游戏的逻辑:只有点击在一个空的单元格才认为是有效的一次移动。

*null值通过xaml扩展标记设置,你可以从附录A中获取更多信息。

5-10显示了当鼠标盘旋在一个空单元格上,黄色高亮显示,;图5-11显示了当鼠标盘旋在一个非空单元格上,不具备黄色高亮显示。

5-10

5-11

当一个用户与显示你的程序状态的控件进行交互时,属性触发器时非常值得关注的。然而,我们也相要关注程序的状态何时改变,正如一个特定的玩家走了一步,和相应地更新样式的设置。为了这点,我们有了数据触发器。

5.6.4数据触发器

不同于属性触发器——只检查WPF依赖属性,数据触发器可以检查任何任何旧有地.NET对象属性。当属性触发器普遍用于检查WPF可视化元素地属性时,数据触发器通常用于检查用于内容的非可视化对象的属性,正如示例5-28PlayerMove对象。

示例5-28

DataTrigger元素使用在Style.Trigger元素中,正如属性触发器一样,这里可以有多于一个是活动的在任何时刻。一个属性触发器在显示内容的可视化元素的属性上操作,而数据触发器在其自身进行操作。这我们这种情形下,每一个单元格的内容都是一个PlayerMove对象。在我们的两个数据触发器中,我们绑定了PlayerName属性。如果值为X,我们就设置前景色为红色;如果为O,就设置为绿色。

#当你使用数据触发器的时候要小心。在我们的示例中,我们获得Button类型的样式和名为CellTextStyle的样式作为潜在的选择。这一章我已经写过两次了,每次开始我们都把数据触发器放在按钮样式而不是数据模板的样式中。数据触发器是基于内容的,因此确定你把它们放入了你的内容样式中,而不是你的控件样式。

既然我们已经转移到数据模板——在图5-5中用编程方式设置了样式,我们还没有为每个设置颜色,但是数据触发器给我们带来了之前的那个特征,伴随着我们创建的所有其他样式,正如图5-12所示。

不同于属性触发器——以来于依赖属性通知的改变,数据触发器以来于标准的数据改变通知模式的实现。这些内嵌到.NET的模式将在第4张讨论,例如InotifyPropertyChanged。由于每一个PlayerMove对象都是常量,我们不必实现这个模式,但是你还要使用数据触发器,偶而你需要在你的自定义内容类中实现它。

数据触发器另一种便利的特征是,不必显示地检查null的内容。如果内容为空,这个触发器条件会自动为false,这是为什么应用程序试着从一个空的PlayerMove中获取PalyerName属性,却没有崩溃的原因。

5-12

5.6.5多重条件数据触发器

正如属性触发器使用MultiTrigger元素编译到“and”条件中,数据触发器也可以这么编译,使用MultiDataTrigger元素。例如,如果我们想要监视游戏的第10次移动,确保是玩家O,以及做一些特殊的事情,正如示例5-29

示例5-29

示例5-29唯一看上去有点奇怪的是,使用了映射语法从.NET编译集中引进了System命名空间。我们这么做是为了可以把10作为一个整型而不是字符型;否则,多重条件数据触发器不会正确匹配我们的MoveNumber属性。示例5-29的多重条件数据触发器将MoveNumber的背景色设置为黄色,意味着一个根据——祝贺这次特殊的移动,而正常的TTT并没有,但是你可以使用多重条件数据触发器为你自己的种类。

5.6.6事件触发器

属性触发器监视依赖属性的值,数据触发器监视CLR属性的值,而事件触发器监视事件。当一个事件发生,例如Click事件,事件触发器响应激发一个相关的动画行为。动画相当具有挑战性,所以要在第8章介绍。示例5-30阐明了一个简单的动画,当一个空单元格被点击时,这个格子在5秒内从深黄色转换到白色。

示例5-30

添加一个动画到一个样式需要两件事情。首先是一个StoryBoard,带有命名的时间线来描述你想要发生的。在我们的情形中,我们的动画是,按钮的背景笔刷颜色在5秒内从黄色转换到白色。

#对于任何带有嵌入式路径的动画的属性,需要有一个显示的属性设置,创建顶级的嵌套。示例5-30中,这意味着我们需要一个Setter元素为Background属性。如果没有创建顶级的嵌套,在运行期就不会有任何动画效果。

第二件需要做的是事件触发器开始了时间线。在我们这种情形,当用户点击应用了CellButtonStyle样式的按钮,如ButtonBackground,我们开始了这个动作——在StoryBoard中由命名的时间线描述。

#写到这里,如果你有一个事件触发器和一个多条件属性触发器,具有相同的原型动画,如ButtonBackground,确保你把多条件触发器放在了xaml文件中的事件触发器前面;否则,你会得到一个荒谬的运行期错误。

这个动画的结果是,显示了黄色的各种阴影,经过点击的效果如图5-13所示。

5-13

属性触发器和数据触发器使你设置属性——当属性改变的时候。事件触发器使你触发事件——当事件发生的时候。这两套触发器是相当不同的,例如,你不能使用事件触发器设置一个属性;或者使用属性触发器和数据触发器激活一个事件。所有这些触发器使你增加了一定程度的交互性到应用程序——以一种极好的声明式的方式,具有极少代码或没有代码。

5.7控件模板

如果仔细的看我们当前的TTT游戏,会发现Button对象并没有完全为我们工作。哪些TTT面板有内圆角?

5-14

这里,我们真正需要的是能够保持按钮的行为,如支持内容和点击事件,但是我们想要接管这些按钮的外观。WPF允许这种方式,因为内在的控件创建的时候是缺少外观性的,例如,他们提供行为,但是外观可以被完全包装在客户端控件的外面。

还记得我们是如何使用数据模板,来为非可视化对象提供外观的么?我们能够使用控件模板对控件做同样的事情,这将是一组StoryBoard,触发器,以及大多数重要的提供控件外观的元素。

为了修复我们的按钮外观,我们创建了一个控件模板的资源。让我们从示例5-31出发,这是一个带有简单的矩形,和以后考虑如何显示实际的按钮内容。

示例5-31

5-15显示了设置一个单独按钮的Template属性的结果。

注意到按钮过去样子的痕迹(保留在图5-15中)。不幸的是,看不到我们的矩形的痕迹。问题在于,缺少一个显示的填充设置,这个矩形的默认填充是透明的,显示grid的黑色背景。让我们将其设置为喜欢的万圣节颜色:

<ControlTemplate x:Key=”ButtonTemplate”>

            <Rectangle Fill=”Orange” />

</ControlTemplate>

5-15

现在我们在这个地方,如图5-16所示。

5-16

注意,拐角处是如何成直角的?而且,一旦你点击了按钮,你不会获得压下的效果。(而且我没有意味“一个不爽的感觉”)

5.7.1控件模板和样式

注意到我们在控件模板上取得的一些成果,让我们将其复制到其它按钮上。我们可以手动设置每个按钮上的模板属性,或者,作为最普通的,我们可以用按钮的样式包装这个模板控件。如示例5-32

示例5-32

正如示例5-32所示,模板属性和使用样式设置是一样的。图5-17显示了结果。

5-17

仍然,橙色是不和谐的,尤其是因为白色背景样式上的设定。我们可以用模板绑定来解决这个问题。

5.7.2模板绑定

为了回到我们的白色按钮,我们可以硬编码将矩形填充为白色,但是如果样式要改变它呢(正如在我们中断的动画)?取代以硬编码填充矩形,我们使用模板绑定来将模板应用到控件属性中,正如示例5-33所示。

示例5-33

模板绑定就像数据绑定,除了绑定的属性来自被你替换了模板的控件(称为模板化的父级别)。在我们的情形中,像BackgroundHorizontalContentAlignment等等,是美丽的游戏,用来模板绑定自父级别。同时,像数据绑定,模板绑定是相当小巧的——用来保持模板中的条目属性是最新的,随着外界的属性改变,如被样式和动画设置等等。举例来说,图5-18显示了混淆矩形的Fill属性到按钮的Background属性的效果——仍然适当地通过我们的点击动画和鼠标盘旋的行为。

5-18

尽管如此,我们还没有彻底到达。如果我们将要改变图画样品,这样图5-18已经变为了一个可玩的游戏,我们不得不显示所有的移动。为了这么做,我们需要一个内容推荐者。

5.7.3内容推荐者

如果你曾经被广告牌或汽车站长椅上写着的“这里是你的广告!”所驱动,然后这就是所有你需要知道的理解内容推荐者。内容推荐者等价于WPF中的“这里是你的内容”,允许内容由插入的ContentContainer控件保持在运行期。

在我们的情形中,内容是可视化的PlayerMove对象。取代以复制所有的工作到按钮新的控件模板中,我们只想要去除它在正确的地方。内容推荐者的工作是获取内容——由内容模板化的父级别提供,以及所有必须要显示的事物,包括样式,触发器等等。内容推荐者可以添加到你的模板中——无论在哪里看到的模板(包括多次,如果它使你愉快,例如生成一个下拉阴影)。在我们的情形中,我们在示例5-34中组成一个内容推荐者,使用第2章的技术在grid中放一个矩形。

示例5-34

在示例5-34中,内容推荐者的Content属性绑定到ContentControl.Content属性,为了内容成功使用。作为使用样式,我们可以避免给模板绑定属性名称加上类的前缀,通过在ContentTemplate元素上设置TargetAttribute属性。

省略7行代码。。。

进一步,在恰当的位置使用TargetType属性,你可以一起去除显示地模板绑定到Content,属性上,同时它会进行自动设置。

省略7行代码。。。

内容推荐者是我们需要的全部,使得我们的游戏回到具有功能性,正如图5-19所示。

5-19

5.7.4真实的工作

最后一小块工作是获取右间隙。由于内容推荐者没有自身的Padding属性,我们不能直接绑定Padding属性(它也没有Background属性,这是为什么我们使用Rectangle和其Fill属性)。因为这些属性并不匹配内容推荐者,你不得不找到映射或者组合提供这些功能的元素。例如,padding是控件中一定数量的空白,另一方面,Margin是控件周围一定数量的空白。由于他们都是同样的类型,System.Windows.Thickness,如果我们可以映射按钮中的Padding到内容控件的外面。我们的TTT游戏看起来就会很漂亮:

省略若干行代码。。。

5-20显示了我们最终的TTT变体。

5-20

就像PaddingMargin间的映射,建立一个元素提供给你想要的外观,并且从父级别的模板绑定到相应的属性,将要做很多的工作来创建你自己的控件模板。

5.8我们进行到哪里了?

样式支持你定义一个策略来设置可视化元素的依赖属性。属性的设置可以被命名以及手动或者编程方式地通过名称应用,或者使用元素类型样式来自动应用。除了提供不变的依赖属性值之外,样式可以包含基于条件的属性值——基于依赖属性,数据属性或者事件。还有,如果设置属性并不足以获取到你寻求的外观,你可以替换一个非可视化的控件,使用控件模板生成全部的控件行为。

但这并不是样式的全部。要了解动画是如何工作的,你可以阅读第8章;要了解样式是如何关联到资源和主题以及皮肤的,你可以阅读第6章。

posted @ 2008-03-20 20:43  包建强  Views(1149)  Comments(0Edit  收藏  举报