第 12 章
样 式
在第11章学习了WPF资源系统,使用资源可以在一个地方定义对象而在整个标记中使用它们。尽管可以使用资源存储各种对象,但是使用资源最常见的原因是通过它们保存样式。
样式是可以应用到元素的属性值的集合。WPF样式系统和HTML标记中的层叠样式表(cascading style sheet,CSS)标准扮演类似的角。和CSS类似,通过WPF样式可以定义格式化特性集合,并且为了保证一致性,在整个应用程序中应用它们。同CSS一样,WPF样式也可以自动工作,指定具体的元素类型,并且通过元素树层叠起来。然而,WPF样式的功能更加强大,因为它们能够设置任何依赖项属性。这意味着可以使用它们标准化未格式化的特性,如控件的行为。WPF样式还支持触发器,当一个属性发生变化时可以通过触发器改变控件的样式(正如将要在本章中介绍的那样),并且能够使用模板重新定义控件的内置外观(将在第15章中介绍这些内容)。一旦学习了如何使用样式,就可以在所有的WPF应用程序中使用它们。
12.1  样式基础
在第11章中已学习过,资源具有几个重要的优点,包括简化标记和使应用程序更容易维护。那么样式有哪些优点呢?
为了理解适合使用样式的场合,分析一个简单的示例十分有帮助。设想需要标准化在窗口中使用的字体。最简单的方法是设置包容器窗口的字体属性。这些属性是在Control类中定义的,包括FontFamily属性、FontSize属性、FontWeight属性(用于粗体)、FontStyle属性(用于斜体)以及FontStretch属性(用于压缩的或扩展的变体)。幸运的是,这些属性值具有继承特性,当在窗口级别上设置这些属性时,在窗口中的所有元素都会使用相同的属性值,除非显式地覆盖它们。
注意:
属性值继承是依赖项属性提供的许多可选的特性之一。在第6章已介绍了依赖项属性。
现在考虑一种不同的情况,希望只为用户界面中的一部分锁定字体。如果能够在一个特定包容器中隔离这些元素(例如,它们都处于一个Grid面板或StackPanel面板中),可以使用本质上相同的方法,并设置包容器的字体属性。但问题并不总是这么简单。例如,可能希望使所有的按钮具有一致的字体和文本尺寸,并且使用和其他元素不同的字体设置。对于这种情况,就需要另外一种方法,在某个地方定义这些细节,并在所有应用它们的地方重用这些细节。
资源提供了一个解决方案,但是有些笨拙。因为在WPF中没有Font对象(只有与字体属性
第12章样式
相关的集合),所以需要定义几个相关的资源,如下所示:
<Window.Resources>
<FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
<sys:Double x:Key="ButtonFontSize">18</s:Double>
<FontWeight x:Key="ButtonFontWeight">Bold</FontWeight>
</Window.Resources>
上面的标记为窗口添加了三个资源:第一个资源是FontFamily对象,该资源包含希望使用
的字体名称;第二个资源是存储数字18的double对象;第三个资源是一个枚举值FontWeight.Bold。假定已经将.NET名称空间系统映射到XAML名称空间前缀,如下所示:
<Window xmlns:sys="clr-namespace:System;assembly=mscorlib" ... >
提示:
当使用资源设置属性时,正确匹配属性类型是非常重要的。这时WPF使用类型转换器的
方式和直接设置一个特性值是不同的。例如,如果正在为元素设置FontFamily特性,可以使用
字符串“Times New Roman”,因为FontFamilyConverter转换器会创建所需要的FontFamily对
象。但如果试图使用字符串资源设置FontFamily属性,情况就不同了——这时,XAML解析器
会抛出一个异常。
一旦定义了所需要的资源,下一步是在元素中实际使用这些资源。因为在整个应用程序的
生命周期中,这些资源永远不会发生变化,所以使用静态资源比较合理,如下所示:
<Button Padding="5" Margin="5" Name="cmd"
FontFamily="{StaticResource ButtonFontFamily}"
FontWeight="{StaticResource ButtonFontWeight}"
FontSize="{StaticResource ButtonFontSize}"
>A Customized Button
</Button>
这个示例可以工作,并且它将字体细节移出了标记。但该示例也存在两个问题:
●(除了资源名称相似之外)没有明确指明这三个资源是相互关联的。这使维护应用程序变
得复杂。如果希望设置更多的字体属性,或者决定为不同类型的元素维护不同的字体
设置,这一问题会变得更加严重。
●使用资源设置属性的标记非常繁琐。实际上,还没有原来不使用资源时简明(直接为元
素定义字体属性)。
可以通过定义一个将所有字体细节捆绑在一起的自定义类(如FontSetting类),来解决第一
个问题。然后可以创建FontSetting对象资源,并在属性中使用它的各种属性。然而,这仍然需
要使用繁琐的资源——并且还需要做一些额外的工作。
样式对这一问题提供了非常好的解决方案。可以定义一个独立的包装所有希望设置的属性
的样式。如下所示:
<Window.Resources>
<Style x:Key="BigFontButtonStyle">
<Setter Property="Control.FontFamily" Value="Times New Roman" />
<Setter Property="Control.FontSize" Value="18" />
<Setter Property="Control.FontWeight" Value="Bold" />
</Style>
</Window.Resources>
289
WPF编程宝典——使用C# 2008和.NET 3.5(第2版)
290
上面的标记创建了一个独立的资源:一个System.Windows.Style对象。这个样式对象包含了一个Setter集合,该集合具有三个Setter对象,每个Setter对象用于一个希望设置的属性。每个Setter对象由两部分信息组成:希望进行设置的属性和希望为该属性应用的数值。和所有的资源一样,样式对象有一个键名,当需要时可以从集合中提取它。对于该示例,键名是BigFontButtonStyle(根据约定,用于样式的键名通常以“Style”结尾)。
每个WPF元素都可以使用一个样式(或者没有样式),样式通过元素的Style属性(该属性是在FrameworkElement类中定义的)嵌入到元素中。例如,使用上面创建的样式配置一个按钮,需要让按钮指向样式资源,如下所示:
<Button Padding="5" Margin="5" Name="cmd"
Style="{StaticResource BigFontButtonStyle}"
>A Customized Button
</Button>
当然,也可以通过代码设置样式。需要做的全部工作就是使用熟悉的FindResource( )方法,从最近的资源集合中提取样式。下面的代码为一个名称为cmd的Button对象设置样式:
cmdButton.Style = (Style)cmd.FindResource("BigFontButtonStyle");
图12-1显示的窗口中的两个按钮使用了BigFontButtonStyle样式。
图12-1  通过样式重用按钮设置
注意:
样式设置元素的初始外观,但是可以随意覆盖它们设置的这些特征。例如,如果应用了BigFontButtonStyle样式,并且显式地设置了FontSize属性,在按钮标签中的FontSize设置会覆盖样式。理想情况下,不应当依赖这种行为——而是应当创建更多的样式,从而可以在样式级别上设置尽可能多的细节。这样在将来调整用户界面时可以有更大的灵活性,使干扰降到最低。
样式系统增加了许多优点。不仅可以创建多组明显相关的属性设置,而且还可以使标记更加流线型,从而使应用这些设置更加容易。最佳方式是,可以应用样式而不用关心设置了哪些属性。在上一个示例中,字体设置被组织到一个名称为BigFontButtonStyle的样式中。如果以后决定大字体按钮还需要更多的内边距和外边距空间,也可以为Padding属性和Margin属性添加设置器。所有使用样式的按钮会自动采用新的样式设置。
第12章样式
Setters集合是Style类中最重要的属性。但并不是唯一的属性,在Style类中一共有5个重
要的属性,在本章会介绍这些属性。表12-1列出了这些属性。
表12-1  Style类的属性
属性描述
Setters 设置属性值以及自动关联事件处理程序的Setter对象或EventSetter对象的集合
Triggers 继承自TriggerBase类并且能够自动改变样式设置的对象的集合。例如,当另外一个属性改变时,或者当发生某个事件时,可以修改样式
控件的使用
Resources 希望用于样式的资源集合。例如,可能需要使用一个对象设置多个属性。这时,将对象作为资源创建,然后再在Setter对象中使用资源效率会更高效(而不是使用嵌套的标签创建对象,作为每个
Setter对象的一部分)
BasedOn 通过该属性可以创建继承自(并且可以有选择地进行重写)其他样式设置的更加复杂的样式
TargetType 该属性标识应用样式的元素的类型。通过该属性可以创建只影响特定类型元素的样式,并且还可以创建能够为恰当的元素类型自动执行动作的样式
现在,您已经看到了一个使用样式的基本示例,这为进一步深入分析样式模型做好了准备。12.1.1  创建样式对象
在上一个示例中,样式对象是在窗口级别定义的,之后在窗口的两按钮中重用该样式。尽
管这是一种常见的设计,但也不是唯一的选择。
如果希望创建更精细的目标样式,可以使用包容器的Resources集合定义样式,如StackPanel
面板或Grid面板。如果希望在应用程序中重用样式,可以使用应用程序的Resources集合定义
样式。这些是更常用的方法。
严格地讲,不需要同时使用样式和资源。例如,可以通过直接填充特定按钮的样式集合来
定义样式,如下所示:
<Button Padding="5" Margin="5">
<Button.Style>
<Style>
<Setter Property="Control.FontFamily" Value="Times New Roman" />
<Setter Property="Control.FontSize" Value="18" />
<Setter Property="Control.FontWeight" Value="Bold" />
</Style>
</Button.Style>
<Button.Content>A Customized Button</Button.Content>
</Button>
上面的代码虽然可以工作,但显然不是很有用。因为现在无法在其他元素之间共享该样式。
如果只是使用样式设置一些属性(如本示例),就不值得使用这种方法,因为直接设置属性
更加容易。然而,如果正在使用样式的其他特性,并且只希望将它应用到单个元素,这一方法
有时会有用。例如,可以使用该方法为一个元素关联事件处理程序。通过该方法还可以修改部
分元素控件模板(对于这种情况,需要使用Setter.TargetName属性,在元素的内部为特定组件应
用一个设置器,如列表框中的滚动条按钮。有关该技术的更多内容将在第15章中学习)。
291
WPF编程宝典——使用C# 2008和.NET 3.5(第2版)
29212.1.2  设置属性
正如所介绍的,每个Style对象包装了一个Setter对象的集合。每个Setter对象设置元素的单个属性。唯一的限制是设置器只能改变依赖项属性——不能修改其他属性。
在某些情况下,不能使用简单的特性字符串设置属性值。例如,不能使用简单的字符串设置ImageBrush对象(如第11章中使用的显示平铺模式的图像画刷)。对于这种情况,可以使用熟悉的XAML技巧,使用嵌套的元素代替特性。下面是一个示例:
<Style x:Key="HappyTiledElementStyle">
<Setter Property="Control.Background">
<Setter.Value>
<ImageBrush TileMode="Tile"
ViewportUnits="Absolute" Viewport="0 0 32 32"
ImageSource="happyface.jpg" Opacity="0.3">
</ImageBrush>
</Setter.Value>
</Setter>
</Style>
提示:
如果希望在多个样式中(或在同一样式的多个设置器中)重用相同的图像画刷,可以将其定义为资源,然后再在样式中使用资源。
为了标识希望设置的属性,需要提供类和属性的名称。然而,需要的类名不一定是定义属性的类名。也可以是继承了准备设置的属性的派生类。例如,考虑如下版本的BigFontButtonStyle 样式,它使用Button类的引用代替了Control类的引用:
<Style x:Key="BigFontButtonStyle">
<Setter Property="Button.FontFamily" Value="Times New Roman" />
<Setter Property="Button.FontSize" Value="18" />
<Setter Property="Button.FontWeight" Value="Bold" />
</Style>
如果将上面的示例(见图12-1)替换为这个样式,可以得到完全相同的结果。那么这两者之间到底有什么区别呢?对于这种情况,区别在于WPF对可能包含相同的FontFamily属性、FontSize属性以及FontWeight属性,但又不继承自Button类的类的处理方式。例如,如果为Label控件使用了该版本的BigFontButtonStyle样式,就没有效果。WPF简单地略过这三个属性,因为不会应用它们。但如果使用原来的那个样式,字体属性就会影响Label控件,因为Label 类继承自Control类。
提示:
WPF忽略属性而不应用它们的这一事实,意味着使用样式设置的属性,不必是在应用样式的元素中定义的属性。例如,如果设置ButtonBase.IsCancel属性,只有当为按钮设置样式时才会有效果。
在WPF中还存在这样一种情况,在元素框架层次中的多个位置定义了同一个属性。例如,在Control类和TextBlock类中都定义了全部的字体属性(如FontFamily属性)。如果创建应用到

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。