WPF学习系列之七 (样式与行为)

  1. 样式(Styles)是组织和重用格式化选项的重要工具。不是使用重复的标记填充XAML,以设置诸如边距、颜色及字体等细节,而能够建立一系列封装全部这些细节的样式。而后能够在须要之处经过一个属性应用样式。
    行为(behavior)是一个重用用户界面代码的工具,它封装了一些通用的UI功能。若是具备适当的行为,可使用一两行XAML标记将其附加到一个元素,从而能够节省编写和调试代码的工做。
  2. 建立样式对象
    复制代码
    <Window.Resources> <Style x:Key="BigFontButtonStyle"> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style> </Window.Resources>
    复制代码

    上面的标记建立了一个独立的资源:一个System.Windows.Style对象,与全部资源同样,样式对象有一个键名,从而当须要时 能够从集合中提取它,根据约定,用于样式的键名一般以Style结尾。样式经过元素的Style属性(该属性是在FrameworkElement基类中 定义的)插入到元素中,以下所示为按钮使用了以上定义的样式:canvas

    <Button Style="{StaticResource BigFontButtonStyle}">A Button For My Test</Button>

    也可使用代码设置样式,就是使用FindResources()方法从最近的资源集合中提取样式:app

    btnStyleTest1.Style = (Style)btnStyleTest1.FindResource("BigFontButtonStyle");

    样式设置元素的初始外观,可是能够随意覆盖它们设置的这些特性。例如,若是应用了BigFontButtonStyle样式,而且明确地设置了FontSize属性,则在按钮中的FindSize设置就会覆盖样式。
    Setters集合是Style类中最重要的属性,但并非惟一的属性,在Style类中共有5个重要属性:
    QQ图片20140811225238
    ide

  3. 设置样式属性
    每一个Style对象包装了一个Setter对象的集合,每一个Setter对象设置元素的单个属性。惟一的限制是设置器只能改变依赖项属性而不能修改其它属性。
    在某些状况下,不能使用简单的特性字符串设置属性值,此时可使用嵌套的属性元素代替特性,以下是一个示例:
    工具

    复制代码
    <Window.Resources> <Style x:Key="HappyFaceTileStyle"> <Setter Property="Control.Background"> <Setter.Value> <ImageBrush ImageSource="images/happyface.jpg" ViewportUnits="Absolute" Viewport="0,0,32,32" TileMode="Tile"></ImageBrush> </Setter.Value> </Setter> </Style> </Window.Resources>
    复制代码

    若是但愿在多个样式中(或在同同样式的多个设置器中)重用相同的图像画刷,能够将其定义为资源,而后再在样式中使用资源:post

    复制代码
    <Window.Resources> <ImageBrush x:Key="happyFace" ImageSource="images/happyface.jpg" ViewportUnits="Absolute" Viewport="0,0,32,32" TileMode="Tile"/> <Style x:Key="HappyFaceTileStyle"> <Setter Property="Control.Background" Value="{StaticResource happyFace}"></Setter> </Style> </Window.Resources>
    复制代码

    为了标识但愿设置的属性,须要设置类和属性的名称,然而,类名没必要是定义属性的类名,也能够是继承了属性的派生类,以下示例,它使用Button类的引用代替了Control类的引用,此时,若是为Label元素也引用了此样式,哪么对于Label此样式就不起做用:字体

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

    以下样式,Button.FontSize属性和TextBlock.FontSize属性是在它们各自的基类中分别声明,但它们都引用同 一个依赖项属性(也就是说,TextBlock.FontSize和Control.FontSize引用都指向同一个 DependencyProperty对象)。因此,当使用这个样式时,WPF设置FontSize属性两次,最后引用的设置具备优先权,而且被同时引用 到Button和TextBlock对象。动画

    <Style x:Key="BigFontButtonStyle"> <Setter Property="Button.FontSize" Value="20"></Setter> <Setter Property="TextBlock.FontSize" Value="33"></Setter> </Style>

    若是定义的样式中全部的属性都准备用于相同的元素类型,能够设置Style对象的TargetType属性,指定准备应用属性的类来简化样式声明,例如,若是只建立只应用于按钮的样式,可按以下方式建立样式:this

    <Style x:Key="BigFontButtonStyle" TargetType="Button"> <Setter Property="FontFamily" Value="Times New Roman"></Setter> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="FontWeight" Value="Bold"></Setter> </Style>
  4. 关联事件处理程序
    先看下面的示例,在样式设置中使用EventSetter对象为MouseEnter和MouseLeave事件关联事件处理程序:url

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

    下面是事件处理程序:spa

    复制代码
    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 Grid.Row="3" Style="{StaticResource MouseOverHighlightStyle}">Hover over me</TextBlock>

    在WPF中,事件设置器是一种不多使用的技术,而更多的使用的是事件触发器。当处理冒泡路由策略时,事件设置器不是一个好的选择,对因而种状况,在高层次的元素上处理但愿处理的事件一般更容易。

  5. 多层样式
    尽管能够在许多不一样的层次定义任意数量的样式,可是每一个元素一次只能使用一个样式对象,乍一看,这好像是一种限制,但因为属性值继承 和样式继承特性,所以,实际上这种限制并不存在。若是但愿为一组相同的控件使用相同的字体,而又不想为每一个控件应用相同的样式,对于这种状况,能够将它们 放置到一个面板,并设置面板的样式,只要设置的属性具备属性值继承特性,这些值就会被传递到子元素。使用这种模型的属性包括IsEnabled、 IsVisible、Foreground以及全部字体属性。
    对于样式继承特性,能够经过为样式设置 BasedOn 特性在另外一个样式的基础上建立样式,例如如下代码,第二个样式继承了第一个样式的属性设置:

    复制代码
    <Style x:Key="BigFontButtonStyle"> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style> <Style x:Key="EmphasizedBigFontButtonStyle" BasedOn="{StaticResource BigFontButtonStyle}"> <Setter Property="Control.Background" Value="White"></Setter> <Setter Property="Control.Foreground" Value="DarkBlue"></Setter> </Style>
    复制代码

    除非有特殊缘由要求一个样式继承自另外一个样式(例如,第二个样式是第一个样式的特例,而且只改变了继承来有大量设置中的几个特征),不然不要使用样式继承,由于这种继承会使应用程序更加脆弱。

  6. 经过类型自动应用样式
    能够经过设置样式的TargetType属性为特定类型的元素自动应用样式,并彻底忽略键名,此时,WPF其实是隐式地使用类型标记扩展设置键名,以下代码所示:

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

    以下示例,在一个窗口中为样式设置TargetType特性为Button,此样式就会被应用到这个窗口中的全部按钮上:

    <Style TargetType="Button"> <Setter Property="FontFamily" Value="Times New Roman"></Setter> <Setter Property="FontSize" Value="20"></Setter> <Setter Property="FontWeight" Value="Bold"></Setter> </Style>

    能够为一个元素的样式特性设置null值来删除样式:

    <Button Style="{x:Null}">Test Button</Button>

    尽管自动样式很方便,可是它会使设计变得复杂,下面是几条缘由。为了不出现此问题,最好果断的使用自动样式为整个用户界面提供一个单一且一致的外观,而后再为特例设置明确的样式。

    • 在具备许多样式和多层样式的复杂窗口中,跟踪是否经过属性值继承或经过样式设置了某个特定属性有些困难,所以,若是但愿改变一个简单的细节,须要查看整个窗口的所有标记。
    • 窗口中的格式化化操做在开始时一般更通常,可是会愈来愈详细,若是刚开始为窗口应用了自动样式,在许多地方可能须要经过显式的样式覆盖自动样式,这会使整个设计变得复杂。为每一个但愿设置的格式化特征的组合建立命名的样式,并根据名称应用它们会更直观。
    • 若是为TextBlock建立了一个自动样式,会同时修改使用TextBlock的其它控件。
  7. 触发器
    使用触发器,能够自动完成简单的样式改变,例如,当一个属性发生变化时能够进行响应,并自动调整样式。触发器在Style的 Triggers集合中定义,每一个样式均可以有任意多个触发器,每一个触发器都是System.Windows.TriggerBase的派生类的实例。
    QQ截图20140814234527
  8. 简单触发器
    能够为任何依赖项属性关联一个简单触发器。每一个触发器都指定了正在监视的属性,以及正在等待的值,当该属性值出现时,将自动应用存储在Trigger.Setters集合中的设置。例以下面的触发器等待按钮获取键盘焦点,当获取焦点时会将其前景色设置为深红色:
    复制代码
    <Style x:Key="BigFontButtonStyle"> <Style.Setters> <Setter Property="Control.FontFamily" Value="Times New Roman"></Setter> <Setter Property="Control.FontSize" Value="20"></Setter> <Setter Property="Control.FontWeight" Value="Bold"></Setter> </Style.Setters> <Style.Triggers> <Trigger Property="Control.IsFocused" Value="True"> <Setter Property="Control.Foreground" Value="DarkRed"></Setter> </Trigger> </Style.Triggers> </Style> 
    复制代码

    触发器的优势是不须要为翻转它们而编写任何逻辑,只要中止应用触发器,元素就会恢复到它的正常外观。能够建立一次应用相同元素的多个触发器,若是这些触发器设置不一样的属性,这种状况就不会出现混乱,但若是多个触发器修改相同的属性,那么最后的触发器有效。 
    若是但愿建立只有当几个条件都为真时才激活的触发器,可使用 MultiTrigger。它提供了一个 Conditions 集合,能够经过该属性定义一系列属性和值的组合,在下例中,只有当按钮具备焦点且鼠标悬停在该按钮上时,才会应用样式:

    复制代码
    <Style x:Key="BigFontButtonStyle"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="Control.IsFocused" Value="True"></Condition> <Condition Property="Control.IsMouseOver" Value="True"></Condition> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property="Control.Foreground" Value="DarkRed"></Setter> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style>  
    复制代码
  9. 事件触发器
    普通的触发器等待一个属性发生变化,而事件触发器等待一个特定的事件被激发,它也不是经过设置器来改变元素,而是要求用户提供一系列修改 控件的动做,这些动做一般被用于一个动画。下面的示例是事件触发器等待MouseEnter事件,而后动态的在0.2秒内将按钮的字体放大到22个单位:
    复制代码
    <Style x:Key="EventTriggerTest"> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="22"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="FontSize"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style>
    复制代码
    在XAML中,必须在故事板(Storyboard)中定义每一个动画,它为动画提供了时间线,在故事板内部,用户能够定义但愿使用的一个或多个动画对象,每一个动画对象执行本质上相同的任务:在必定时期内修改依赖项属性。
    DoubleAnimation类(同全部动画类,都位于System.Windows.Media.Animation名称空间中):它能在一段给定的时间内将任何双精度值属性逐渐改变为设定的目标值,改变的实际尺寸取决于时间总量和须要改变的总量。
    与属性触发器不一样,若是但愿翻转回原来的状态,须要反转事件触发器。在以上示例中,样式使用了一个响应MouseLeave事件的事件触发器,并在1秒内恢复到原来的大小,此时不须要指明目标字体尺寸,WPF会假定反转回原来的字体尺寸。
  10. 行为,行为旨在封装一些UI功能,从而能够不用编写代码就可以将其应用到元素上。
    样式提供了重用一组属性设置的实用方法,但在典型的应用程序中,属性设置只是用户界面基础结构中的一小部分,而大部分基本的程序一般须要 大量的用户界面代码,这些代码一般与应用程序的功能无关,在许多程序中,用于用户界面的代码(如驱动动画、实现平滑效果、维护用户界面状态)不管是在数量 上仍是在复杂性上都超过了业务代码,而许多这类代码是通用的,因此Expression Elend创做者开发了一个称为行为的特征。其思想是开发人员建立一个封装了一些通用用户界面功能的行为,而后经过将行为拖动到须要行为的控件上,此控件 就具备了行为所定义的一些功能。
    为了得到支持行为特性的程序集,能够安装Expression Blend或安装Expression Blend SDK(下载地址:http://tinyurl.com/kkp4g8):
    System.Windows.Interactivity.dll:它定义了支持行为的基本类,它是行为特征的基础。
    Microsoft.Expression.Interactions.dll:它经过添加可选的以核心行为类为基础的动做和触发器类,增长了一些有用的扩展。
  11. 建立行为
    首先添加对System.Windows.Interactivity.dll的引用,而后建立一个继承自Behavior基类的类。在 此类中覆盖OnAttached()和OnDetaching()方法,在这两个方法中能够经过AssociatedObject属性访问放置行为的元 素。在OnAttached()方法中关联事件处理程序,在OnDetaching()中移除事件处理程序。 如下是建立一个能够为任意元素提供使用鼠标在Canvas面板上拖动元素的行为类 DragInCanvasBehavior:
    复制代码
     1 public class DragInCanvasBehavior : Behavior<UIElement>  2 {  3 private Canvas canvas;  4  5 //如下的OnAttached()和OnDetaching()方法是用于监视MouseLeftButtonDown、MouseMove、MouseLeftButtonUp事件的代码。  6 protected override void OnAttached()  7  {  8 base.OnAttached();  9 10 // Hook up event handlers. 11 this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown; 12 this.AssociatedObject.MouseMove += AssociatedObject_MouseMove; 13 this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp; 14  } 15 16 protected override void OnDetaching() 17  { 18 base.OnDetaching(); 19 20 // Detach event handlers. 21 this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown; 22 this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove; 23 this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp; 24  } 25 26 // Keep track of when the element is being dragged. 27 private bool isDragging = false; 28 29 // When the element is clicked, record the exact position 30 // where the click is made. 31 private Point mouseOffset; 32 33 //当用户单击鼠标左键时,DragInCanvasBehavior开始拖动操做,记录元素左上角与鼠标指针之间的偏移,并捕获鼠标。 34 private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 35  { 36 // Find the canvas. 37 if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas; 38 39 // Dragging mode begins. 40 isDragging = true; 41 42 // Get the position of the click relative to the element 43 // (so the top-left corner of the element is (0,0). 44 mouseOffset = e.GetPosition(AssociatedObject); 45 46 // Capture the mouse. This way you'll keep receiveing 47 // the MouseMove event even if the user jerks the mouse 48 // off the element. 49  AssociatedObject.CaptureMouse(); 50  } 51 52 //当元素处于拖动模式且移动鼠标时,从新定位元素。 53 private void AssociatedObject_MouseMove(object sender, MouseEventArgs e) 54  { 55 if (isDragging) 56  { 57 // Get the position of the element relative to the Canvas. 58 Point point = e.GetPosition(canvas); 59 60 // Move the element. 61 AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y); 62 AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X); 63  } 64  } 65 66 //当释放鼠标时,结束拖动。 67 private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 68  { 69 if (isDragging) 70  { 71  AssociatedObject.ReleaseMouseCapture(); 72 isDragging = false; 73  } 74  } 75 }
    复制代码
  12. 使用行为
    首先在项目中添加对定义DragInCanvasBehavior类的类库及 System.Windows.Interactivity.dll程序集的引用,而后在窗口XML中映射这两个名称空间。只须要使用 Interaction.Behaviors附加属性在Canvas面板中添加任意元素。以下是建立了一个具备三个元素的Canvas面板,其中两个元素 使用了DragInCanvasBehavior行为:
    复制代码
    <Window x:Class="WpfApplication1.DragInCanvasTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:custom="clr-namespace:CustomBehaviousLibrary;assembly=CustomBehaviousLibrary" Title="DragInCanvasTest" Height="300" Width="300"> <Grid> <Canvas> <Rectangle Canvas.Left="36" Canvas.Top="49" Height="46" Name="rectangle1" Stroke="Black" Width="76" Fill="#FFC00000"> <i:Interaction.Behaviors> <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior> </i:Interaction.Behaviors> </Rectangle> <Ellipse Canvas.Left="82" Canvas.Top="126" Height="69" Name="ellipse1" Stroke="Black" Width="39" Fill="#FF2323D1"> <i:Interaction.Behaviors> <custom:DragInCanvasBehavior></custom:DragInCanvasBehavior> </i:Interaction.Behaviors> </Ellipse> <Ellipse Canvas.Left="164" Canvas.Top="103" Height="34" Name="ellipse2" Stroke="Black" Width="41" Fill="#FF1CAA1C" /> </Canvas> </Grid> </Window>
    复制代码
相关文章
相关标签/搜索