样式提供了重用一组属性设置的实用方法。它们为帮助构建一致的、组织良好的界面迈出了重要的第一步——但是它们也是有许多限制。

  问题是在典型的应用程序中,属性设置仅是用户界面基础结构的一小部分。甚至最基本的程序通常也需要大量的用户界面代码,这些代码与应用程序的功能无关。在许多程序中,用于用户界面任务的代码(如驱动动画、实现平滑效果、维护用户界面状态,以及支持诸如拖放、缩放以及停靠等用户界面特性)无论是在数量山还是复杂性上都超出了业务代码。许多这类代码是通用的,这意味着在创建的每个WPF对象中需要编写相同的内容。所有这些工作几乎都单调乏味的。

  为回应这一挑战,Expression Blend创作者开发了称为行为(behavior)的特征。其思想很简单:创建封装了一些通用用户界面功能的行为。这一功能可以是基本功能(如启动故事板或导航到超链接),也可以是复杂功能(如处理多点触摸交互,或构建使用实时物理引擎的碰撞模型)。一旦构建功能,就可将它们添加到任意应用程序的另一个控件中,具体方法是将该控件链接到适当的行为并设置行为的属性。在Expression Blend中,只通过拖放操作就可以使用行为。

一、获取行为支持

  重用用户界面的代码通用块得基础结构不是WPF的一部分。反而,它被捆绑到Expression Blend。这是因为行为开始时作为Expression Blend的设计时特性引入的。但这并不意味着行为只能用于Expression Blend。只需要付出很少的努就可以在Visual Studio应用程序中的创建和使用行为。只需要手动缩写标记,而不是使用工具箱。

  为了获得支持行为的程序集,有两种选择:

  •   可按照Expression Blend 3、Expression Blend 4 或Visual Studio 2012以上版本。所有这些版本都包含Visual Studio中的行为功能所需的程序集。
  •   可安装Expression Blend SDK。

  无论是使用Expression Blend的哪个版本,都可以在文件夹中看到所需的两个相同的重要程序集:

  •   System.Windows.Interactivity.dll。这个程序集定义了支持行为的基本类。它是行为特征的基础。
  •   Microsoft.Expression.Interactions.dll。这个程序集通过添加可选的以核心行为类为基础的动作和触发器类,增加了一些有用的扩展。

二、理解行为模型

  行为特性具有两个版本:一个版本是旨在Silverlight添加行为支持,Silverlight是Microsoft的针对浏览器的富客户端插件;而另一个版本是针对WPF设计的。尽管这两个版本提供了相同的特性,但行为特性和Silverlight领域更吻合,因为它弥补了更大的鸿沟。与WPF不同,Silverlight不支持触发器,所以实现行为的程序集也实现触发器更合理。然而,WPF支持触发器,行为特性包含自己的触发器系统,而触发器系统与WPF模型不匹配,这确实令人感到有些困惑。

  问题在于具有类似名称的这两个特性有部分重合但不完全相同。在WPF中,触发器最重要的角色是构建灵活的样式和控件模板。在触发器的帮助下,样式和模板变得更加智能;例如,当一些熟悉发生变化时可应用可视化效果。然而,Expression Blend中的触发器系统具有不同的目的。通过使用可视化设计工具,允许为应用程序添加简单功能。换句话说,WPF触发器支持更加强大的样式和控件模板。而Expression Blend触发器支持快速的不需要代码的应用程序设计。

  那么,对于使用WPF的普遍开发人员来说所有这些意味着什么呢?下面是几条指导原则:

  •   行为模型不是WPF的核心部分,所以行为不像样式和模板那样确定。换句话说,可编写不使用行为的WPF应用程序,但如果不使用样式和模板,就不能创建比“Hello World”演示更复杂的WPF应用程序。
  •   如果在Expression Blend上耗费大量时间,或希望为其他Expression Blend用户开发组件,可能会对Expression Blend中的触发器特性感兴趣。尽管和WPF中的触发器系统使用相同的名称,但它们不能相互重叠,可以同时使用这两者。
  •   如果不使用Expression Blend,可完全略过触发器特性——但仍应分析Expression Blend提供的功能完整的行为类。这是因为行为比Expression Blend的触发器更强大也更常用。最终,将准备查找那些提供了可在自己的应用程序中使用的整洁美观行为的第三方组件。

三、创建行为

  行为旨在封装一些UI功能,从而可以不比编写代码就能够将其应用到元素上。从另一个角度看,每个行为都为元素提供了一个服务。该服务通常涉及监听几个不同的事件并执行几个相关的操作。

  为更好地理解行为,最好自己创建一个行为。设想希望为任意元素提供使用鼠标在Canvas面板上拖动元素的功能。对于单个元素实现该功能的基本步骤是非常简单的——代码监听鼠标事件并修改设置相应Canvas坐标的附加属性。但通过付出更多一点的努力,可将该代码转换为可重用的行为,该行为可为Canvas面板上的所有元素提供拖动支持。

  在继续之前,创建一个WPF类库程序集。在该程序集中,添加System.Windows.Interactivity.dll程序集的引用。然后,创建一个继承自Behavior基类的类。Behavior是通用类,该类使用一个类型参数。可使用该类型参数将行为限制到特定的元素,或使用UIElement或FrameworkElement将它们都包含进来。如下所示:

public class DragInCanvasBehavior : Behavior<UIElement>
{ }

  在任何行为中,第一步要覆盖OnAttached()和OnDetaching()方法。当调用OnAttached()方法时,可通过AssociatedObject属性访问放置行为的元素,并可关联事件处理程序。当调用OnDetaching()方法时,移除事件处理程序。

  下面是DragInCanvasBehavior类用于监视MouseLeftButtonDown、MouseMove以及MouseLeftButtonUp事件代码:

 protected override void OnAttached()
{
base.OnAttached(); // Hook up event handlers.
this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
} protected override void OnDetaching()
{
base.OnDetaching(); // Detach event handlers.
this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
}

  最后一步是在事件处理程序中运行适当的代码。例如,当用户单击鼠标左键时,DragInCanvasBehavior开始拖动操作、记录元素左上角与鼠标指针之间的偏移并捕获鼠标:

private Canvas canvas;
// Keep track of when the element is being dragged.
private bool isDragging = false; // When the element is clicked, record the exact position
// where the click is made.
private Point mouseOffset; private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Find the canvas.
if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas; // Dragging mode begins.
isDragging = true; // Get the position of the click relative to the element
// (so the top-left corner of the element is (0,0).
mouseOffset = e.GetPosition(AssociatedObject); // Capture the mouse. This way you'll keep receiveing
// the MouseMove event even if the user jerks the mouse
// off the element.
AssociatedObject.CaptureMouse();
}

  当元素处于拖动模式并移动鼠标时,重新定位元素:

private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
// Get the position of the element relative to the Canvas.
Point point = e.GetPosition(canvas); // Move the element.
AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
}
}

  当释放鼠标键时,结束拖动:

private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
AssociatedObject.ReleaseMouseCapture();
isDragging = false;
}
}

  DragInCanvasBehavior类的完整代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media; namespace CustomBehaviorsLibrary
{
public class DragInCanvasBehavior : Behavior<UIElement>
{
private Canvas canvas; protected override void OnAttached()
{
base.OnAttached(); // Hook up event handlers.
this.AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
} protected override void OnDetaching()
{
base.OnDetaching(); // Detach event handlers.
this.AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
this.AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
} // Keep track of when the element is being dragged.
private bool isDragging = false; // When the element is clicked, record the exact position
// where the click is made.
private Point mouseOffset; private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Find the canvas.
if (canvas == null) canvas = VisualTreeHelper.GetParent(this.AssociatedObject) as Canvas; // Dragging mode begins.
isDragging = true; // Get the position of the click relative to the element
// (so the top-left corner of the element is (0,0).
mouseOffset = e.GetPosition(AssociatedObject); // Capture the mouse. This way you'll keep receiveing
// the MouseMove event even if the user jerks the mouse
// off the element.
AssociatedObject.CaptureMouse();
} private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
// Get the position of the element relative to the Canvas.
Point point = e.GetPosition(canvas); // Move the element.
AssociatedObject.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
AssociatedObject.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
}
} private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (isDragging)
{
AssociatedObject.ReleaseMouseCapture();
isDragging = false;
}
}
}
}

DragInCanvasBehavior

四、使用行为

  为测试行为,创建一个新的WPF应用程序项目。然后添加对定义DragInCanvasBehavior类的类库以及System.Windows.Interactivity.dll程序集得应用。接下来在XML中映射这两个标记名称。假定存储DragInCanvasBehavior类的类库名为CustomBehaviorsLibrary,所需的标记如下所示:

<Window x:Class="BehaviorTest.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:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary"
Title="DragInCanvasTest" Height="300" Width="300">

  为使用该行为,只需要使用Interaction.Behaviors附加属性在Canvas面板中添加任意元素。下面的标记创建一个具有三个图形的Canvas面板。两个Ellipse元素使用了DragInCanvasBehavior,并能在Canvas面板中拖动。Rectangle元素没有使用DraginCanvasBehavior,因此无法移动。

<Canvas>
<Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60"></Rectangle>
<Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
<Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
</Canvas>

  下图显示了该例的运行效果。

  

DragInCanvasTest完整代码:
<Window x:Class="BehaviorTest.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:CustomBehaviorsLibrary;assembly=CustomBehaviorsLibrary"
Title="DragInCanvasTest" Height="300" Width="300">
<Canvas>
<Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow" Width="40" Height="60"></Rectangle>
<Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
<Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70">
<i:Interaction.Behaviors>
<custom:DragInCanvasBehavior></custom:DragInCanvasBehavior>
</i:Interaction.Behaviors>
</Ellipse>
</Canvas>
</Window>

DragInCanvasTest

  但这并非是全部内容。如果正在使用Expression Blend进行开发,行为甚至提供了更好的设计体验——可以根本不用编写任何标记。

【WPF学习】第三十八章 行为的更多相关文章

  1. 【WPF学习】第十八章 多点触控输入

    多点触控(multi-touch)是通过触摸屏幕与应用程序进行交互的一种方式.多点触控输入和更传统的基于笔(pen-based)的输入的区别是多点触控识别手势(gesture)——用户可移动多根手指以 ...

  2. 【WPF学习】第二十八章 程序集资源

    WPF应用程序中的程序集资源与其他.NET应用程序中的程序集资源在本质上是相同的.基本概念是为项目添加文件,从而Visual studio可将其嵌入到编译过的应用程序的EXE或DLL文件中.WPF程序 ...

  3. “全栈2019”Java第三十八章:类与方法

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. Gradle 1.12用户指南翻译——第三十八章. Eclipse 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  5. angular学习笔记(三十)-指令(10)-require和controller

    本篇介绍指令的最后两个属性,require和controller 当一个指令需要和父元素指令进行通信的时候,它们就会用到这两个属性,什么意思还是要看栗子: html: <outer‐direct ...

  6. angular学习笔记(三十)-指令(7)-compile和link(2)

    继续上一篇:angular学习笔记(三十)-指令(7)-compile和link(1) 上一篇讲了compile函数的基本概念,接下来详细讲解compile和link的执行顺序. 看一段三个指令嵌套的 ...

  7. angular学习笔记(三十)-指令(7)-compile和link(1)

    这篇主要讲解指令中的compile,以及它和link的微妙的关系. link函数在之前已经讲过了,而compile函数,它和link函数是不能共存的,如果定义了compile属性又定义link属性,那 ...

  8. angular学习笔记(三十)-指令(6)-transclude()方法(又称linker()方法)-模拟ng-repeat指令

    在angular学习笔记(三十)-指令(4)-transclude文章的末尾提到了,如果在指令中需要反复使用被嵌套的那一坨,需要使用transclude()方法. 在angular学习笔记(三十)-指 ...

  9. angular学习笔记(三十)-指令(5)-link

    这篇主要介绍angular指令中的link属性: link:function(scope,iEle,iAttrs,ctrl,linker){ .... } link属性值为一个函数,这个函数有五个参数 ...

  10. angular学习笔记(三十)-指令(2)-restrice,replace,template

    本篇主要讲解指令中的 restrict属性, replace属性, template属性 这三个属性 一. restrict: 字符串.定义指令在视图中的使用方式,一共有四种使用方式: 1. 元素: ...

随机推荐

  1. $Noip2014/Luogu2296$ 寻找道路 图论

    $Luogu$ $Sol$ 首先找出符合条件一的点然后跑$SPFA$就好了叭. 如何判断点是否符合条件一呢?先连反边,记录每个点的入度,然后从终点开始$dfs$,记录每个点被到达的次数,若到达的次数等 ...

  2. SpringBoot整合Thymeleaf-基于SpringBoot2.X版本

    1.为啥要用Thymeleaf模板引擎?现在不都前后端分离了么? 熊dei们,别着急,我们先来谈谈为啥开始用Thymeleaf模板引擎,先照顾照顾下我们这些可爱的小白童鞋.... 为啥开始用Thyme ...

  3. 【Javascript函数】节流throttle和间隔控制dbounce

    一.throttle 函数节流,指把很小时间内触发的N多事件,节流成1个事件. 我们这里说的throttle就是函数节流的意思.再说的通俗一点就是函数调用的频度控制器,是连续执行时间间隔控制.主要应用 ...

  4. 悄摸直播(二)—— 播流器实现(拉取rtmp服务器中的数据流,播放直播画面)

    悄摸直播 -- JavaCV实现本机摄像头画面远程直播 播流器 一.功能说明 从rtmp服务器中获取视频流数据 + 展示直播画面 二.代码实现 /** * 播流器 * @param inputPath ...

  5. python 枚举类型

    在python中枚举是一种类(Enum,IntEnum),存放在enum模块中.枚举类型可以给一组标签赋予一组特定的值. 枚举的特点: 枚举类中不能存在相同的标签名 枚举是可迭代的 不同的枚举标签可以 ...

  6. 2次方的期望dp

    某一天WJMZBMR在打osu~~~但是他太弱逼了,有些地方完全靠运气:(    我们来简化一下这个游戏的规则    有n次点击要做,成功了就是o,失败了就是x,分数是按comb计算的,连续a个com ...

  7. Jetbrains CLion 安装及配置详解

    # Hi 今天呢就给大家推荐一个高大上 强大智能的 C/C++语言编辑器.ta的名字叫-- Jetbrains CLion (呃,说好了不用标题字呢) 这个编辑器呢主要支持C和C ++,包括现代C + ...

  8. 想玩转JAVA高并发,这些概念你必须懂!

    我们在找工作时,经常在招聘信息上看到有这么一条:有构建大型互联网服务及高并发等经验,你第一时间想到的是媒体常说的双十一吗?带着问题,我们一起思考技术…. 高并发高并发 它是互联网分布式系统架构设计中必 ...

  9. 小白学Java:奇怪的RandomAccess

    目录 小白学Java:奇怪的RandomAccess RandomAccess是个啥 forLoop与Iterator的区别 判断是否为RandomAccess 小白学Java:奇怪的RandomAc ...

  10. wtforms 钩子函数

    参考: https://www.cnblogs.com/wupeiqi/articles/8202357.html class LoginForm(Form): name = simple.Strin ...