【WPF学习】第三十八章 行为

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

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

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

1、获取行为支持工具

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

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

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

  不管是使用Expression Blend的哪一个版本,均可以在文件夹中看到所需的两个相同的重要程序集:this

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

2、理解行为模型spa

  行为特性具备两个版本:一个版本是旨在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的触发器更强大也更经常使用。最终,将准备查找那些提供了可在本身的应用程序中使用的整洁美观行为的第三方组件。

3、建立行为

  行为旨在封装一些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

4、使用行为

  为测试行为,建立一个新的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进行开发,行为甚至提供了更好的设计体验——能够根本不用编写任何标记。

相关文章
相关标签/搜索