【WPF学习】第三十六章 样式基础

  前面三章介绍了WPF资源系统,使用资源可在一个地方定义对象而在整个标记中重用他们。尽管可以使用资源存储各类对象,但使用资源最多见的缘由之一是经过他们的保存样式。app

  样式是可应用于元素的属性值集合。WPF样式系统与HTML标记中的层叠样式表(Cascading Style Sheet,CSS)标准担当相似的角色。与CSS相似,经过WPF样式可定义通用的格式化特性集合,而且为了保证一致性,在整个应用程序中应用他们。与CSS同样,WPF样式也可以自动工做,指定具体的元素类型为目标,并经过元素树层叠起来。然而,WPF样式的功能更增强大,由于他们可以设置任何依赖项属性。这意味着可使用它们标准化未格式化的特性,如控件的行为。WPF样式也支持触发器(trigger),当属性发生变化时,可经过触发器改变控件的样式,而且可以使用模板从新定义控件的内置外观。一旦学习了如何使用样式,就能够在全部WPF应用程序中使用他们。框架

  为了理解适合使用样式的场合,分析一下简单的示例是有帮助的。设想须要标准化在窗口中使用的字体。最简单的方法是设置包含窗口的字体属性。这些属性是在Control类中定义的,包括FontFamily、FontSize、FontWeight(用于粗体)、FontStyle(用于斜体)以及FontStretch(用于压缩或扩展的变体)。得益于这些属性值得继承特性,当在窗口级别设置这些属性时,窗口中的全部元素都会使用相同的属性值,除非明确地覆盖它们。ide

  如今考虑一种不一样情形,但愿只为用户界面中的一部分锁定字体。若是能在特定的容器中隔离这些元素(例如,它们都处于Grid或StackPanel面板中),就可使用本质上相同的方法,并设置容器的字体属性。但问题未必老是这么简单。例如,可能但愿使用全部按钮具备一致的字体和文本尺寸,并使用和其余元素不一样的字体设置。对于这种状况,就须要一种方法在某个地方定义这些细节,并在全部应用它们的地方重用这些细节。工具

  资源提供了一个解决方案,但有些笨拙。由于WPF中没有Font对象(只有与字体属性相关的集合),因此须要定义几个相关的资源,以下所示:学习

<Window.Resources>
        <FontFamily x:Key="ButtonFontFamily">Times New Roman</FontFamily>
        <s:Double x:Key="ButtonFontSize">18</s:Double>
        <FontWeight x:Key="ButtonFontWeight">Bold</FontWeight>
</Window.Resources>

  上面的代码片断(标记)为窗口添加了三个资源:第一个资源是FontFamily对象,该对象包含但愿使用的字体名称;第二个资源是存储数字18的double对象;第三个资源是枚举值FontWeight.Bold。假定已将.NET名称空间System映射到XAML名称空间前缀s。以下所示:字体

<Window x:Class="Styles.ReuseFontWithResources"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        Title="ReuseFontWithResources" Height="300" Width="300">

  一旦定义所需的资源,下一步就是在元素中实际使用这些资源。由于在应用程序的整个生命周期中,这些资源永远不会发生变化,因此使用静态资源是合理的,以下所示:动画

<Button Padding="5" Margin="5"
            FontFamily="{StaticResource ButtonFontFamily}"
            FontWeight="{StaticResource ButtonFontWeight}"
            FontSize="{StaticResource ButtonFontSize}" 
              >A Customized Button</Button>

  这个示例能够工做,它将字体细节(所谓的magic number)移出了标记。但该例也存在两个问题:spa

  除了资源名称类似外,没有明确指明这三个资源是相关的。这使维护应用程序变得复杂。若是须要设置更多字体属性,或决定为不一样类型的元素维护不一样的字体设置,这个问题尤其严重。设计

  须要使用资源的标记很是繁琐。实际上,尚未原来不使用资源时简明(直接在元素中定义字体属性)。3d

  可经过定义将全部字体细节捆绑在一块儿的自定义类(如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>

  上面的标记建立了一个独立资源:一个System.Windows.Style对象。这个样式对象包含了一个设置器集合,该集合具备三个Setter对象,每一个Setter对象用于一个但愿设置的属性。每一个Setter对象由两部分信息组成:但愿进行设置的属性的名称和但愿为该属性应用的值。与全部资源同样,样式对象都有一个键名,从而当须要时能够从集合中提取它。在该例中,键名是BigFontButtonStyle(根据约定,用于样式的键名一般以Style结尾)。

  每一个WPF元素均可使用一个样式(或者没有样式),样式经过元素的Style属性(该属性是在FrameworkElement基类中定义的)插入到元素中。例如,要使用上面建立的样式配置按钮,须要让按钮指向样式资源,以下所示:

<Button Margin="5" Padding="5" Style="{StaticResource BigFontButtonStyle}">A Customized Button</Button>

  固然,也可经过代码设置样式。须要作的所有工做就是使用你们熟悉的FindResource()方法,从最近的资源集合中提取样式。下面的代码为名为cmd的Button对象设置样式:

cmdButton.Style=(Style)cmd.FindResource("BigFontButtonStyle");

  以下图所示,窗口中的两个按钮使用了BigFontButtonStyle样式:

 

   样式系统增长了许多优势。不只可建立多组明显相关的属性设置,并且使得应用这些设置更加容易,从而精简了标记。最让人满意的是,可应用样式而不用关心设置了哪些属性。在上一个示例中,字体设置被组织到名为BigFontButtonStyle的样式中。若是之后决定大字体按钮还须要更多的内边距和外边距空间,也可为Padding和Margin属性添加设置器。全部使用样式的按钮会自动采用新的样式设置。

  Setters集合是Style类中最重要的属性,但并不是惟一属性。Style类中共有5个重要属性。下表列出了这些属性。

表 Style类的属性

 

 1、建立样式对象

  在上一个示例中,样式对象时在窗口级别定义的,以后再窗口的两个按钮中重用该样式。尽管这是一种常见的设计方式,但并不是是惟一的选择。

  若是但愿建立目标更加精细的样式,可以使用容器的Resources集合定义样式,如StackPanel面板或Grid面板。若是但愿在应用程序中重用样式,可以使用应用程序的Resources集合定义样式。这些也是经常使用的方法。

  严格来将,不须要同事使用样式和资源。例如,可经过直接填充特定按钮的样式集合来定义样式,以下所示:

 <Button Margin="5" Padding="5">
            <Button.Style>
                <Style>
                    <Setter Property="Control.FontFamily" Value="Times New Roman"/>
                    <Setter Property="Control.FontSize" Value="20"/>
                    <Setter Property="Control.FontWeight" Value="Bold" />
                </Style>
            </Button.Style>
            <Button.Content>A Customized Button</Button.Content>
        </Button>

  上面的代码虽然可凑效,但显然不是颇有用,由于如今没法与其余元素共享该样式。

  若是只使用样式设置一些属性,就不值得使用这种方法。由于直接设置属性更加容易。然而,若是正在使用样式的其余特性,而且只但愿将它应用到单个元素。这一方法有时会有用。例如,可以使用该方法为元素关联触发器,还能够经过该方法修改元素控件模板的一部分(对于这种状况,须要使用Setter.TargetName属性,为元素内部的特定组件应用设置器,如列表框中的滚动条按钮)。

2、设置属性

  正如已经看到的,每一个Style对象都封住了一个Setter对象的集合。每一个Setter对象设置元素的单个属性。惟一的限制是设置器只能改变依赖项属性——不能修改其余属性。

  在某些状况下,不能使用简单的特新字符串设置属性值。例如,不能使用简单字符串建立ImageBrush对象。对于此类状况,可以使用你们熟悉的XAML技巧,用嵌套的元素代替特性。下面是一个示例:

<Style x:Key="HappyTiledElementStyle">
            <Setter Property="Control.Background">
                <Setter.Value>
                    <ImageBrush TileMode="Tile" ViewportUnits="Absolute"
                                ImageSource="happyface.jpg" Viewport="0 0 32 32" Opacity="0.3"/>
                </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>

  若是将上面的样式进行替换后,将获得相同的结果。那么二者之间到底有什么区别呢?对于这种状况,区别在于WPF对可能包含相同的FontFamily、FontSize以及FontWeight属性,但又不是继承自Button的其余类的处理方式。例如,若是为Label控件使用该版本的BigFontButtonStyle样式,就没有效果。WPF简单地忽略这三个属性,由于不会应用他们。但若是使用原样式,字体属性就会影响就会影响Label控件,由于Label类继承自Control类。

  在WPF中还存在这样一些状况,在元素框架层次中的多个位置定义了同一个属性。例如,在Control和TextBlock类中都定义了所有的字体属性(如FontFamily)。若是正在建立应用到TextBlock对象以及继承自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" />

            <Setter Property="TextBlock.FontFamily" Value="Times New Roman"/>
            <Setter Property="TextBlock.FontSize" Value="18"/>
        </Style>

  然而,这样不会获得指望的结果。问题在于,尽管Button.FontFamily和TextBlock.FontFamily属性是在各自的基类中分别声明,但它们都引用同一个依赖性属性(换句话说,TextBlock.FontSizeProperty和Control.FontSizeProperty引用都指向同一个DependencyProperty对象。)。因此,当使用这个样式时,WPF设置FontFamily和FontSize属性两次。最后应用的设置具备优先权,并同时应用到Button和TextBlock对象。尽管这个问题很是特别,许多属性并不存在该问题,但若是常常建立为不一样的元素类型应用不一样格式的样式,分析是否存在这一问题就显得很重要了。

  还可以使用另外一种技巧简化样式声明。若是全部属性都准备用于相同的元素类型,就设置Style对象的TargetType属性来指定准备应用属性的类。例如,若是建立只应用于按钮的样式,可按以下方式建立样式:

<Style x:Key="BigFontButtonStyle" TargetType="Button">
      <Setter Property="FontFamily" Value="Times New Roman"/>
      <Setter Property="FontSize" Value="18"/>
      <Setter Property="FontWeight" Value="Bold" />
</Style>

  这样方法比较方便。正如将在后面分析的,若是不使用样式键名,TargetType属性还可做为自动应用样式的快捷方式。

3、关联事件处理程序

  属性设置器是全部样式中最多见的要素,但也能够建立为事件关联特定事件处理程序的EventSetter对象的集合。下面列举的示例为MouseEnter和MouseLeave事件关联事件处理程序:

 <Style x:Key="MouseOverHighlightStyle">
            <Setter Property="TextBlock.Padding" Value="5"/>
            <EventSetter Event="FrameworkElement.MouseEnter" Handler="element_MouseEnter" />
            <EventSetter Event="FrameworkElement.MouseLeave" Handler="element_MouseLeave" />
 </Style>

  下面的事件处理代码:

private void element_MouseEnter(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
        }
        private void element_MouseLeave(object sender, MouseEventArgs e)
        {
            ((TextBlock)sender).Background = null;
        }

  MouseEnter和MouseLeave事件使用直接事件路由,这意味着他们再也不元素树中冒泡和隧道移动。若是但愿为大量元素应用鼠标悬停其上的效果(例如,当鼠标移动到元素上时,但愿改变元素的背景色),须要为每一个元素添加MouseEnter和MouseLeave事件处理程序。基于样式的事件处理程序简化了这项任务。如今只须要应用单个样式,该样式包含了属性设置器和事件设置器:

<TextBlock Style="{StaticResource MouseOverHighlightStyle}">Hover over me.</TextBlock>

  下图显示了该技术的一个简单演示程序,该程序中有三个元素,其中两个元素使用了MouseOverHighlightStyle样式。

 

  该示例完整xaml文件:

<Window x:Class="Styles.EventSetter"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="EventSetter" Height="300" Width="300">
    <Window.Resources>
        <Style x:Key="MouseOverHighlightStyle">
            <Setter Property="TextBlock.Padding" Value="5"/>
            <EventSetter Event="FrameworkElement.MouseEnter" Handler="element_MouseEnter" />
            <EventSetter Event="FrameworkElement.MouseLeave" Handler="element_MouseLeave" />
        </Style>
    </Window.Resources>

    <StackPanel>
        <TextBlock Style="{StaticResource MouseOverHighlightStyle}">Hover over me.</TextBlock>
        <TextBlock Padding="5">Don't bother with me.</TextBlock>
        <TextBlock Style="{StaticResource MouseOverHighlightStyle}">Hover over me.</TextBlock>
    </StackPanel>
</Window>
EventSetter

  WPF极少使用事件设置器这种技术。若是须要使用此处演示的功能,可能更喜欢使用事件触发器,它以声明方式定义了所但愿的的行为。事件触发器是专为实现动画而设计的,当建立鼠标悬停效果时它们更有用。

  当处理使用冒泡路由策略的事件时,事件设置器并不是好的选择。对于这种状况,在高层次的元素上处理但愿处理的事件一般更容易。例如,若是但愿将工具栏上的全部按钮链接到同一个Click事件处理程序,最好为包含全部按钮的Toolbar元素关联单个事件处理程序。对于这种状况,不必使用事件设置器。

4、多层样式

  尽管可在许多不一样层次定义任意数量的样式,但每一个WPF元素一次只能使用一个样式对象。乍一看,这像是一种限制,但因为属性值继承和样式继承特性,这种限制实际上并不存在。

  例如,假设但愿为一组控件使用相同的字体,又不想为每一个控件应用相同的样式,对于这种状况,可将它们放置到面板(或其余类型的容器)中,并设置容器的样式。只要设置的属性具备属性值继承特性,这些值就会被传递到子元素。使用这种模型的属性包括IsEnabled、IsVisible、Foreground以及全部字体属性。

  对于另一些状况,可能但愿在另外一个样式的基础上建立样式。可经过为样式设置BasedOn特性来使用此类样式继承。例如,分析下面的两个样式:

<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>

        <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
            <Setter Property="Control.Foreground" Value="White" />
            <Setter Property="Control.Background" Value="DarkBlue" />
        </Style>
    </Window.Resources>

  第一个样式(BigFontButtonStyle)定义了三个字体属性。第二个样式(EmphasizedBigFontButtonStyle)从BigFontButtonStyle样式获取这些属性设置,而后经过另外两个改变前景色和背景色的画刷属性对它们进行了增长。经过使用这种分红两部分的设计方式,可只应用字体设置,也能够应用字体设置和颜色设置的组合。经过这种设计还可建立包含已经定义的字体或颜色细节的更多样式。

  下图显示了样式继承在一个简单窗口中的工做状况,该窗口使用了这两个样式:

 

   该示例完整XAML以下:

<Window x:Class="Styles.StyleInheritance"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleInheritance" Height="300" Width="300">
    <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>

        <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
            <Setter Property="Control.Foreground" Value="White" />
            <Setter Property="Control.Background" Value="DarkBlue" />
        </Style>
    </Window.Resources>

    <StackPanel Margin="5">
        <Button Padding="5" Margin="5"
            Style="{StaticResource BigFontButtonStyle}" 
              >Uses BigFontButtonStyle</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <Button Padding="5" Margin="5"
            >A Normal Button</Button>
        <TextBlock Margin="5">More normal Content.</TextBlock>
        <Button Padding="5" Margin="5"
            Style="{StaticResource EmphasizedBigFontButtonStyle}" 
              >Uses EmphasizedBigFontButtonStyle</Button>
    </StackPanel>
</Window>
StyleInheritance

5、经过类型自动应用样式

  到目前位置,已讨论了如何建立具备名称的样式以及如何在标记中引用它们。但还有一种方法,能够为特定类型的元素自动应用样式。

  这一工做很是简单。只须要设置TargetType属性以指定合适的类型(如前所述),并彻底忽略键名。这样作时,WPF其实是使用类型标记扩展来隐式地设置键名,以下所示:

x:Key="{x:Type Button}"

 如今,样式已自动应用于整个元素树中的全部按钮上。例如,若是在窗口中采用这种方式定义了一个样式,它会被应用到窗口中的每一个按钮上(除非有一个更特殊的样式替换了该样式)。

  下面列举一个示例,该例中的窗口自动设置按钮样式。

<Window x:Class="Styles.AutomaticStyles"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="AutomaticStyles" Height="300" Width="300">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="FontFamily" Value="Times New Roman" />
            <Setter Property="FontSize" Value="18" />
            <Setter Property="FontWeight" Value="Bold" />
        </Style>
    </Window.Resources>

    <StackPanel Margin="5">
        <Button Padding="5" Margin="5">Customized Button</Button>
        <TextBlock Margin="5">Normal Content.</TextBlock>
        <Button Padding="5" Margin="5" Style="{x:Null}"
            >A Normal Button</Button>
        <TextBlock Margin="5">More normal Content.</TextBlock>
        <Button Padding="5" Margin="5">Another Customized Button</Button>
    </StackPanel>
</Window>

  在该例中,中间的按钮显示替换了样式。但该按钮并无为本身提供一个新样式,而将Style属性设置为null值,这样就有效地删除了样式。

  尽管自动样式很是方便,但它们会让设计变得复杂。下面列出几条缘由:

  •   在具备许多样式和多层样式的复杂窗口中,很难跟踪是否经过属性值继承或经过样式设置了某个特定属性(若是是经过样式设置的,那么是经过哪一个样式设置的呢?)。所以,若是但愿改变某个简单细节,就须要查看整个窗口的所有标记
  •   窗口中的格式化操做在开始时一般更通常,但会逐渐变得愈来愈详细。若是刚开始为窗口应用了自动样式,在许多地方可能须要使用显示的样式覆盖自动样式。这会使整个设计变得复杂。为每一个但愿设置的格式化特征的组合建立名得样式,并根据名称应用他们会更加直观。
  •   在好比,若是为TextBlock元素建立自动样式,那么会同时修改使用TextBlock的其余控件(如模板驱动的ListBox控件)

  为避免出现这些问题,最好果断地使用自动样式。若是决定使用自动样式为整个用户界面提供单1、一致的外观,可尝试为特例使用明确的样式。

相关文章
相关标签/搜索