原文:
通过把同样的内嵌样式提升到资源中(正如第一章介绍的),我们可以给它一个名字,以及按名字使用它在我们的Button实例上,正如示例5-5。
示例5-5
<!-- Window1.xaml --> < Window > < Window.Resources > < Style x:Key ="CellTextStyle" > < Setter Property ="Control.FontSize" Value ="32" /> < Setter Property ="Control.FontWeight" Value ="Bold" /> </ Style > </ Window.Resources > < Button Style =" {StaticResource CellTextStyle} " x:Name ="cell00" /> </ Window > 在示例
5-5
中,我们在属性中使用到Control
前缀取代Button
前缀,从而允许样式更广泛的应用,正如我们将要看到的。 5.3.1 TargetType属性
方便起见,如果所有的属性可以在一个共享类中设置,像我们示例中的Control,我们可以提升这个类的前缀,放入TargetType属性中,并将其从属性的名称中移除掉。如示例5-6所示。
示例5-6
< Style x:Key ="CellTextStyle" TargetType =" {x:Type Control} " > < Setter Property ="FontSize" Value ="32" /> < Setter Property ="FontWeight" Value ="Bold" /> </ Style > 当提供了一个TargetType
属性,你能够只设置该类型可接受的属性。如果你想要在继承树下扩展一组大量的属性,你可以通过使用一个派生类型来做到这些,如示例5-7
。 示例5-7
< Style x:Key ="CellTextStyle" TargetType =" {x:Type Button} " > <!-- IsCancel is a Button-specific property --> < Setter Property ="IsCancel" Value ="False" /> < Setter Property ="FontSize" Value ="32" /> < Setter Property ="FontWeight" Value ="Bold" /> </ Style > 在这种情形中,
IsCancel
属性只在Button
上有效,因此,为了设置它,我们需要为了这个类型转换TargetType
属性。 你可能想知道为什么我设置FontSize为“32”而不是“32pt”,当详细的指定了字体大小时,后者在一条直线上占得更多,。这两种表示法明显是不相等的。(前者是pixels,而后者是points)。由于这种写法,我使用pixels,WPF样式使用了一个不加前缀的属性,允许“32pt”明确指定为FontSize,然而前缀属性并不是这样。例如,下面的代码能正常运行(假设Target已经被设置了):
< Setter Property =”FontSize” Value =”32pt” /> 而以下并没有这么做(不管TargetType是否被设定)
< Setter Property =”Control.FontSize” Value =”32pt” /> 希望在你读到这里的时候,这个问题已经被修复了。(而且没有被其它所取代) 5.3.2样式复用
除了将你从要为每一个属性名输出类前缀的名称解救出来之外,TargetType属性还会检查所有应用了样式的类是那个类型或派生的类型的一个实例。这意味着一旦我们将TargetType在Control中设置,我们就能将其应用到一个Button元素,而不是一个TextBlock元素,前者最终派生于Control,而后者不是。
另一方面,尽管Control和TextBlock都共享共同的FrameworkElement祖先,FrameworkElement并没有定义一个FontSize依赖属性,因此一个带有FrameworkElement.TargetType的样式不会让我们设置FontSize属性,因为它并不在那里,尽管事实上Control和TextBlock都有一个FontSize属性。
即使在Control中设置TargetType,我们获得了一个跨越类来复用样式的尺度,这些类是派生于Control的,如Button,Label,Window等等。尽管如此,如果我们从样式中一起去除TargetType属性,我们获得了一个跨越控件来复用样式的尺度,不再有一个公有的基础,而是共享一个依赖属性的实现。根据我的经验,我发现依赖属性跨越类共享了同样的名称,如Control.FontSize和TextBlock.FontSize,还共享了一个实现。这意味着即使Control和TextBlock各自定义了它们的FontSize属性,在运行时它们共享这个属性的实现,所以我可以编写示例5-8那样的代码。
示例5-8
< Style x:Key ="CellTextStyle" > < Setter Property ="Control.FontSize" Value ="32" /> </ Style > <!-- derives from Control --> < Button Style =" {StaticResource CellTextStyle} " /> <!-- does *not* derive from Control --> < TextBlock Style =" {StaticResource CellTextStyle} " /> 在示例
5-8
中,我从样式定义中去除了TargetType
属性,使用了替代的雷前缀在每个样式设置的属性上。这个样式能被应用到一个Button
元素,正如你期望的,也能应用到一个TextBlock
控件上,随着FontSize
在样式中明确地设定。这样工作的原因是Button
从Control
获取它的FontSize
依赖属性定义,而TextBlock
,提供它的FontSize
依赖属性定义,共享由TextElement
实现的FontSize
依赖属性。图5-2
显示了元素间的关系及他们的依赖属性实现。 如图5-2所示,如果我们需要,我们可以按照TextElement重新定义我们的样式,即使它陷入机成熟,既不是Control也不是TextBlock,如示例5-9所示。
图5-2
示例5-9
< Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> </ Style > < Button Style =" {StaticResource CellTextStyle} " /> < TextBlock Style =" {StaticResource CellTextStyle} " /> 更进一步,如果我们想要定义一个包含属性的样式,这些属性没有被每个我们要应用样式的元素共享,我们也可以这么做,如示例
5-10
。 示例5-10
< Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> < Setter Property ="Button.IsCancel" Value ="False" /> </ Style > <!-- has an IsCancel property --> < Button Style =" {StaticResource CellTextStyle} " /> <!-- does *not* have an IsCancel property --> < TextBlock Style =" {StaticResource CellTextStyle} " /> 在示例
5-10
中,我们已经添加了Button.IsCancel
属性在CellTextStyle
中,以及将其应用到有这个属性的Button
元素,而TextBlock
元素则没有这个属性。那好,在运行时,WPF
将应用依赖属性到拥有这个属性的元素上 WPF的应用样式到对象的能力,并不要求对象有所有的属性定义在样式中,类似的应用到Word的Normal样式,包含一个字体类型属性,同时包括一段文本和一个图片。即使Word知道图片没有字体类型,它仍然应用到Normal样式的一部分使之确实有意义。(正如对齐属性),忽略其余的内容。
回到我们的示例,我们可以在一个新行中的TextBlock上使用CellTextStyle,以显示轮到谁了,如示例5-11所示:示例5-11:
< Window.Resources > < Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> < Setter Property ="TextElement.FontWeight" Value ="Bold" /> </ Style > </ Window.Resources > < Grid Background ="Black" > < Grid.RowDefinitions > < RowDefinition /> < RowDefinition /> < RowDefinition /> < RowDefinition Height ="Auto" /> </ Grid.RowDefinitions > < Grid.ColumnDefinitions > < ColumnDefinition /> < ColumnDefinition /> < ColumnDefinition /> </ Grid.ColumnDefinitions > < Button Style =" {StaticResource CellTextStyle} " /> < TextBlock Style =" {StaticResource CellTextStyle} " Foreground ="White" Grid.Row ="3" Grid.ColumnSpan ="3" x:Name ="statusTextBlock" /> </ Grid > </ Window > 跨越不同控件的样式复用给我的应用程序一致的外观,如图
5-3
所示。 你应该注意的是图5-3的状态文字,尽管按钮中的文字是黑色的。由于黑色是默认的文字颜色,如果我们想要状态文字的显示相对于黑色背景,我们不得不将颜色改变为其它,因此需要在TextBlock上设置Foreground属性为白色。联合样式,每一个示例属性Setting都工作正常,而且,你可以联合两种设置属性值的技术正如你看到的。
图5-3
5.3.3
进一步,如果我们想要在一个指定的实例上复写一个样式属性,通过在实例上设置属性,我们是可以做到的,正如示例5-12。
示例5-12
< Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> < Setter Property ="TextElement.FontWeight" Value ="Bold" /> </ Style > < TextBlock Style =" {StaticResource CellTextStyle} " FontWeight ="Thin" /> 在示例5-12
中,在TextBlock
实例属性上设置FontWeight
,优先于在样式属性上设置FontWeight
。 5.3.4继承样式属性
为了完成oo的三个特征:复用,复写,继承,你可以从一个基础样式继承一个样式,添加新属性或者重写已有属性,如示例5-13所示。
示例5-13
< Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> < Setter Property ="TextElement.FontWeight" Value ="Bold" /> </ Style > < Style x:Key ="StatusTextStyle" BasedOn =" {StaticResource CellTextStyle} " > < Setter Property ="TextElement.FontWeight" Value ="Thin" /> < Setter Property ="TextElement.Foreground" Value ="White" /> < Setter Property ="TextBlock.HorizontalAlignment" Value ="Center" /> </ Style > BaseOn样式属性用于指定一个基础样式。在示例5-3中,StatusTextStyle样式继承了所有CellTextStyle属性的设置,复写了FontWeight,以及Foreground和HorizontalAlignment的设置。注意到,HorizontalAlignment属性使用了TextBlock前缀,这是因为TextElement没有HorizontalAlignment这个属性。
我们当前使用的样式了引起了TTT游戏看上去如图5-4。
图5-4
到目前为止,我们的应用程序相当不错,尤其是状态文字上的细字体宽度,但是我们可以做得更好。
5.3.5编程上设置样式
一旦一个样式有了名字,从我们的代码中是很好利用的。例如,我们可能决定要每个玩家有自己的样式。在这种情形中,在xaml中,在编译期使用命名属性不会成功,因为我们想基于内容设置样式,而内容是直到运行期才知道的。然而,没有什么要求我们去设置一个控件静态的Style属性;我们也可以编程实现,如示例5-14。
示例5-14
public partial class Window1 : Window { void cell_Click( object sender, RoutedEventArgs e) { Button button = (Button)sender; // Set button content button.Content = this .CurrentPlayer; if ( this .CurrentPlayer == " X " ) { button.Style = (Style)FindResource( " XStyle " ); this .CurrentPlayer == " O " ; } else { button.Style = (Style)FindResource( " OStyle " ); this .CurrentPlayer == " X " ; } } } 在示例5-14
中,无论玩家何时点击,除了设置按钮内容之外,我们从窗体资源中拉出一个命名样式并将其设置给按钮的样式。这假设了一对命名样式在窗体级别被定义,如示例5-15
。 示例5-15
< Window.Resources > < Style x:Key ="CellTextStyle" > < Setter Property ="TextElement.FontSize" Value ="32" /> < Setter Property ="TextElement.FontWeight" Value ="Bold" /> </ Style > < Style x:Key ="XStyle" BasedOn =" {StaticResource CellTextStyle} " > < Setter Property ="TextElement.Foreground" Value ="Red" /> </ Style > < Style x:Key ="OStyle" BasedOn =" {StaticResource CellTextStyle} " > < Setter Property ="TextElement.Foreground" Value ="Green" /> </ Style > </ Window.Resources > 随着这些适当位置的样式,以及伴随着内容的设置按钮样式的代码,我们得到图5-5
。 注意到,所有的X和O都根据命名玩家的样式设置了颜色。在这种特定的情形中(以及其他情形),数据触发器(在5.6中讨论)应该被优先选择用来以编程方式设置样式,但是你不知道什么时候将要必须去堵塞。
作为所有xaml的构造器,你可以自由地以编程方式创建样式。附录A是一个好的介绍——关于如何考虑在xaml和代码之间来来往往的进行。
图5-5