样式和行为
Contents
样式是组织和重用格式化选项的重要工具
行为是一款重用用户界面代码的更有挑战性的工具,基本思想是:使用行为封装一些通用的UI功能(例如,是元素可被拖动的代码)
<Window.Resources>
<Style x:key="BigFontButtonStyle">
<Setter Property="Control.FontFamily" value="Times New Roman"/>
<Setter Property="Control.FontSize" value="18"/>
<Setter Property="Control.FontWeigth" value="Bold"/>
</Style>
</Window.Resources>
每个WPF元素都可使用一个样式(或没有样式)。样式通过元素Style属性(该属性是在FrameworkElement基类中定义的)插入到元素中,要使用上面创建的样式,需要让按钮指向样式资源:
<Button Padding="5" Margin="5" Name="cmd" Style="{StaticResource BigFontButtonStyle}">A Customized Button</Button>
也可以使用代码设置样式
cmdButton.Style=(Style)cmd.FindResource("BigFontButtonStyle");
Style类的属性
属性 | 说明 |
---|---|
Setters | 设置属性值以及自动关联事件处理程序的Setter对象或EventSetter对象的集合 |
Triggers | 继承自TrggerBase类并能自动改变样式设置的对象集合。例如,当另一个属性改变时,或者当发生某个事件时,可以修改样式 |
Resources | 希望用于样式的资源集合。例如,可能需要使用一个对象设置多个属性。这时,更高效的做法是作为资源创建对象,然后在Setter对象总使用该资源(而不是使用嵌套的标签作为没个Setter对象的一部分创建对象) |
BasedOn | 通过该属性可创建继承自(并且可以有选择地进行重写)其他样式设置的更具体样式 |
TargetType | 该属性表示应用样式的元素的类型。通过该属性可创建只影响特定类型元素的设置器,还可以创建能够为恰当的元素类型自动起作用的设置器 |
设置属性
在某些情况下,不能使用简单的特性字符串设置属性值。例如,不能使用简单字符串创建ImageBrush对象。对于此类情况,用嵌套元素代替特性
<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>
如果希望在多个样式中重用相同的图像画刷,可将其定义为资源,然后在样式中使用资源。
为了标识希望设置的属性,需要提供类和属性的名称,然而,使用的类名未必是定义属性的类名,也可以是继承了属性的派生类。例如,如下版本,该样式用Button类引用替代Control类的引用:
<Style x:key="BigFontButtonStyle">
<Setter Property="Button.FontFamily" Value="Times New Roman"></Setter>
<Setter Property="Button.FontSize" Value="18" />
<Setter Property="Button.FontWeight" Value="Bold"/>
</Style>
如果Lable控件使用该版本的样式,就没有效果。如果使用Control.FontFamily登央视,就会影响Label空间,因为Label类继承自Control类。
如果在元素框架中的多个位置定义了同一个属性,例如,在Control和TextBlock类中都定义了全部的字体属性(如FontFamily)。如:
<Style x:key="BigFontStyle">
<Setter Property="Button.FontFamily" Value="Times New Roman"/>
<Setter Property="Button.FontSize" Value="18"/>
<Setter Property="TextBlock.FontFamily" Value="Arial"/>
<Setter Property="TextBlock.FontSize" Value="10"/>
</Style>
然而,这样不会得到期望的结果,尽管Button.FontFamily和TextBlock.FontFamily属性是在各自的基类中分别声明,但他们都引用同一个依赖项属性(话句话说,TextBlock.FontSizeProperty和Control.FontSizeProperty引用都指向头一个DependencyProperty对象)。所以,当使用这个样式,WPF设置FontFamily和FontSize属性两次。最后引用的设置具有优先权,并同时应用到Button和TextBlock对象。尽管这个问题非常特别,许多属性并不存在该问题,但如果经常创建为不同的元素类型应用不同的格式的样式,分析是否存在这一问题就显得很重要。
关联事件处理程序
可以创建为事件关联特定事件处理程序的EventSetter对象的集合
<Style x:key="MouseOverHightlightStyle">
<EventSetter Event="TextBlock.MouseEnter" Handler="element_mouseEnter"/>
<EventSetter Event="TextBlock.MouseLeave" Handler="element_MouseLeave" />
</Style>
MouseEnter和MouseLeave事件使用直接事件路由,这意味着他们不在元素树种冒泡和隧道移动。如果希望为大量元素应用鼠标悬停其上的效果(例如,当鼠标移动到元素上时,希望改变元素的背景色),需要为每个元素添加MouseEnter和MouseLeave事件处理程序。基于样式的时间处理程序简化了这项任务。现在只需要应用单个样式,该样式包含了属性设置其和事件设置器
<TextBlock Style="{StaticResource MouseOverHighlightStyle}">
Hover over me.
</TextBlock>
多层样式
尽管可在许多不同层次定义任意数量的样式,但每个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="Blod"/>
</Style>
<Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}">
<Setter Property="Control.Foreground" Value="White" />
<Setter Property="Control.Background" Value="DarkBlue"/>
</Style>
</Window.Resources>
除非有特殊原因要求一个样式继承自另一个样式,否则不要使用样式继承
通过类型自动应用样式
只需要设置TargetType属性以指定合适的类型,并完全忽略键名
<Window.Resources>
<Style TargetType="Button">
<Setter Property="FontFamily" Value="Times New Roman" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Blod"/>
</Style>
</Window.Resources>
现在,样式已自动应用于整个元素树的所有按钮上(除非有一个更特殊的样式替换了该样式)
<Button Style="{x:Null}">A Normal Button</Button>
尽管自动样式非常方便,但他们会让设计变得复杂。
– 在具有许多样式和多层样式的复杂窗口中,很难跟踪是否通过属性值继承或通过样式设置了某个特定属性(如果是通过样式设置的,那么是通过那个样式设置的呢?)。
– 窗口中的格式化操作在开始时通常更一般,但会逐渐变得越来越详细。如果刚开始为窗口应用了自动样式,在许多地方可能需要使用显式的样式覆盖自动样式,这会使整个设计变得复杂。为每个希望设置的格式化特征的组合创建命名的样式,并根据名称应用他们会更加直观。
– 再比如,如果为TextBlock元素穿件自动样式,那么会自动修改使用TextBlock的其他空间(比如模板驱动的ListBox控件)
触发器
使用触发器,可自动完成简单的样式改变,而这通常需要使用样板事件处理逻辑
触发器通过Style.Triggers集合链接到样式。每个样式都可以有任意多个触发器,而且每个触发器都是System.Windows.TriggerBase的派生类的实例。
名称 | 说明 |
---|---|
Trigger | 这是一种最简单的触发器。可以检测依赖项属性的变化,然后使用设置器改变样式 |
MultiTrigger | 与Trigger类似,但这种触发器联合了多个条件。只有满足了所有这些条件,才会启动触发器 |
DataTrigger | 这种触发器使用数据绑定。与Trigger类似,只不过监视的是任意绑定数据的变化 |
MultiDataTrigger | 联合多个数据触发器 |
EventTrigger | 这是最复杂的触发器。当事件发生时,这种触发器应用动画 |
通过使用FrameworkElement.Triggers集合,可直接为元素应用除法器,而不需要创建样式。但是存在一个相当大的缺陷。这个Triggers集合只支持事件触发器
简单触发器
可为任何依赖项属性关联简单触发器,比如,可通过响应Control类的IsFocused、IsMouseOver以及IsPressed属性变化,创建鼠标悬停效果和焦点效果。
当属性值出现时,将应用春出在Trigger.Setters集合中的设置器(但不能使用更复杂的触发器逻辑,例如,比较某个值已查看是否处于某个范围,或执行某种计算等,对于这种情况,最好使用时间处理程序)
<Style x:key="BigFontButton">
<Style.Setters>
<Setter Property="Control.FontFamily" Value="Times New Roman"/>
<Setter Property="Control.FontSize" Value="18"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="Control.IsFocused" Value="True">
<Setter Property="Control.Foreground" Value="DarkRed"/>
</Trigger>
</Style.Triggers>
</Style>
注意:
本质上,触发器是众多覆盖从依赖项属性返回的值的属性提供者之一。但原始值(不管是本地设置还是通过样式设置的)仍会保留,只要触发器被禁用,触发器之前的属性值就会再次可用。
在这个实例中,要得到没管的按钮,触发器不能满足全部需求,还要收到按钮控件模板的限制,空间模板锁定了按钮外观的某些特定方面。当自定义元素时,为了得到这种程度的最佳结果,需要使用控件模板,然而,控件模板不能代替触发器,实际上,控件模板经常使用触发器以充分利用这两个特征:可以完全的自定义控件,并且可以响应鼠标悬停、单击以及其他事件来改变他们可视化外观的某些方面。
如果希望创建只有当几个条件都为真时才激活的触发器,可使用MultiTrigger。这种触发器提供了一个Conditions集合
<Window.Resources>
<Style x:Key="BigFont">
<EventSetter Event="TextBlock.MouseEnter" Handler="FrameworkElement_MouseEnter"></EventSetter>
<EventSetter Event="TextBlock.MouseLeave" Handler="FrameworkElement_MouseLeave"/>
<Setter Property="Button.FontFamily" Value="华文行楷"/>
<Setter Property="Button.FontSize" Value="50"/>
<Setter Property="TextBlock.FontFamily" Value="华文行楷"/>
<Setter Property="TextBlock.FontSize" Value="50"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Control.IsFocused" Value="true" />
<Condition Property="Control.IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="ItemsControl.Foreground" Value="DarkRed"/>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
不必关心条件顺序,因为在改变背景色之前,这些条件都必须保持为真
事件触发器
普通重复阿奇等待属性发生变化,而事件触发器等待特定事件被引发。事件触发器要求用户提供一系列修改空间的动作,这些动作通常被用于动画
<Window.Resources>
<Style x:Key="BigFont">
<EventSetter Event="TextBlock.MouseEnter" Handler="FrameworkElement_MouseEnter"></EventSetter>
<EventSetter Event="TextBlock.MouseLeave" Handler="FrameworkElement_MouseLeave"/>
<Setter Property="Button.FontFamily" Value="华文行楷"/>
<Setter Property="Button.FontSize" Value="50"/>
<Setter Property="TextBlock.FontFamily" Value="华文行楷"/>
<Setter Property="TextBlock.FontSize" Value="50"/>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Control.IsFocused" Value="true" />
<Condition Property="Control.IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="ItemsControl.Foreground" Value="DarkRed"/>
</MultiTrigger.Setters>
</MultiTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="22"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
与属性触发器不同,如果希望元素返回到原始状态,需要反转时间触发器
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1" Stroyboard.TargetProperty="FontSize"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
不需要指明目标字体尺寸——如果没有指明该目标,WPF假定您希望使用第一次动画之前的按钮原来的字体尺寸
行为
创建封一些通用用户界面功能的行为。
获取行为支持
行为被捆绑到Expression Blend中,为了获得支持行为的程序集,有两种选择
– 可安装Expression Blend
– 可安装Expression Blend SDK
所需的程序集
– System.Windows.Interactivity.dll、这个程序集定义了支持行为的基本类,他是行为特征的基础
– Microsoft.ExpressionInteractions.dll。这个程序集通过添加可选的以核心行为类为基础的动作和触发器类,增加了一些有用的扩展。
理解行为模型
行为特性包含自己的触发器系统,而触发器系统与WPF模型不匹配
– 行为模型不是WPF核心部分,所以行为不想样式和模板那样确定
– 尽管Blend和WPF中的触发器系统使用相同的名称,打他们不互相重叠,可以同事使用两者
– 如果不适用Blend,可完全忽略其触发器特征
创建行为
创建一个WPF类库程序集(在这个实例中,该程序及成为CustomBehaviorsLibrary).添加对System.Windows.Interactivity.dll程序集的引用,。然后创建一个继承自Behavior基类的类。
public class DragInCanvasBehavior:Behavior<UIElement>
{
}
在理想情况下,不必自己创建行为,而是使用其他人已经创建好的行为。
在任何行为中,第一是覆盖OnAttached()和OnDetaching()方法。当调用OnAttached()方法时,可通过AssociatedObject属性访问防止行为的行为,并可关联事件处理程序。当调用OnDetaching()方法时,移除事件处理程序
使用行为
<Window xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.WIndows.Interactivity"
xmlns:custom="clr-namespace:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary" ...>
</Window>
为了使用该行为,只需要使用Interaction.Behaviors附加属性在Canvas面板中添加任意元素。
<Canvas>
<Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior>
</custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
</Canvas>