Spiga

WPF学习笔记1:知识串讲

2022-08-21 10:24:56

一、理解XAML

1. 项目表现形式

  • 项目结构:项目文件,对象文件(XAML + C#)
  • XAML
    • eXtensible Application Markup Language的英文缩写,相应的中文名称为可扩展应用程序标记语言
    • XML:Extensible Markup Language
    • 标记定义
  • 命名空间
    • 默认
    • 映射:x(XAML语言本身所需的类型和属性)/d(代表设计时相关的工具对象)/mc(标记兼容功能)
    • x:Array x:Key x:Name x:Null x:Static x:Share x:Type

2. 视觉树与逻辑树

  • 查看分析工具:Snoop
  • 层级关系
    • 逻辑开发
    • 视觉呈现

3. 标签扩展

  • 自定义标签/对象
  • 继承相关基类
  • 命名空间引入

4. 类型转换器

  • XAML字符串编辑
  • 对象属性非字符类型转换

二、WPF基础控件

控件类型 主要控件
按钮控件 Button、RepeatButton、RadioButton
数据显示控件 TextBlock、Label、Image、ItemsControl、ListView、ListBox、DataGrid、DocumentViwer
输入控件 TextBox、RichTextBox、CheckBox、ComboBox、DatePicker、PasswordBox、Slider、ProgressBar
菜单导航控件 MenuItem、ContextMenu、ToolBar、TreeView、TabControl、Expander
信息提示控件 Popup、Window、PrintDialog、ToolTip
布局控件 Grid、StackPanel、WrapPanel、DockPanel、UniformGrid、Canvas、InkCanvas、Border
图形控件 Line、Rectangle、Ellipse、Polyline、Polygon、Path、3D
容器控件 ScrollViwer、GroupBox、ViewBox

1. 常用属性

  • 尺寸(宽高)

  • 定位(Margin,HorizontalAlignment、VerticalAlignment)

  • 颜色(Background、Foreground)

  • 信息显示(Text、Content、ListViewItem、ListBoxItem、DataGridTextColumn…..)

  • 鼠标事件、键盘事件

  • 特别属性

    • RadioButton:GroupName
    • 集合控件:AlternationCount
    • ComboBox:SelectedItem、SelectedValue、SelectedValuePath、DisplayMemberPath、SelectedIndex
    • DatePicker:SelectedDate、DisplayDateStart、DisplayDateEnd
    • PasswordBox:Password(普通属性)
    • Silder、ProgressBar:最小值、最大值、当前值
    • Popup:IsOpen、Placement、PlacementTarget、StaysOpen
    • Window:无边框:1、WindowStyle=“None” AllowsTransparency=“True“ 2、WindowChrome

2. 基础控件继承关系树

三、 平面图形

1. 图像对象

Rectangle、Ellipse、Line、Polyline、Polygon、Path

属性:

  • Fill:填充色
  • Stroke:线条颜色
  • StrokeThickness:线条的厚度(线宽)
  • StrokeDashArray:虚线控制(数组)
  • StrokeDashOffset:虚线起始位置的偏移
  • StrokeDashCap:虚线段的两端样式(向外延伸三角、半圆、方形)
  • StrokeEndLineCap、StrokeStartLineCap:整体线条的结束、起始端样式
  • StrokeMiterLimit:交叉点锐角向外延伸距离
  • StrokeLineJoin:交叉点的锐角样式

2. 几何图形对象

Rectangle、Ellipse、Line、GeometryGroup、CombinedGeometry、PathGeometry

ArcSegment :

  • 弧线结束坐标Point
  • 弧线所在椭圆的横纵半轴尺寸Size
  • 弧线所在椭圆的旋转角度RotationAngle
  • 弧两点间弧线的扫掠方向SweepDirection
  • 在两点的弧线是否取大弧(优势弧)IsLargeArc

3. 微语言

  • M(Move移动到XY)
  • L(Line终点XY)并不是必须,可以省略
  • H(水平移动X)、V(垂直移动Y)
  • C(三次贝塞尔曲线)、Q(二次贝塞尔曲线)、S(平滑三次贝塞尔曲线)、T(平滑二次贝塞尔曲线)
  • A(Arc:尺寸HV、旋转角度、大弧、扫掠方向、坐标XY)
  • 大小写:大写标记绝对坐标、小写标记相对坐标(相对前一个坐标变化值)
  • z 图形封闭

4. DrawingContext

  • OnRender
  • 方法:DrawLine、DrawRectangle、DrawRoundedRectangle、DrawEllipse、DrawImage、DrawVideo、DrawGeometry

5. 图形渲染

  • SolidColorBrush:纯色填充
  • LinearGradientBrush:沿一条直线方向混合两种或多种颜色的渐变效果
  • RadialGradientBrush:从一个中心点向外呈圆形辐射的渐变效果
  • ImageBrush:使用图像来填充
  • DrawingBrush:使用矢量图形(形状、几何图形组合)、图像或文本等轻量级绘图填充
  • VisualBrush:将另一个控件(如Button、Panel)或其视觉树投影到填充区域,实现类似“镜像”的效果
  • BitmapCacheBrush:VisualBrush的高性能版本。通过将视觉内容缓存为位图来提升渲染效率

6. 对象变形

  • TranslateTransform:位移
  • RotateTransform:旋转
  • ScaleTransform:缩放
  • SkewTransform:斜切
  • MatrixTransform:矩阵
  • TransformGroup/执行顺序

7. 对象效果 Effect

  • DropshadowEffect:

    • Direction:阴影显示方向,逆时针角度值
    • Color:颜色
    • ShadowDepth:阴影深度
    • BlurRadius:模糊程度
    • Opacity:阴影透明度
  • BlurEffect

  • 自定义去色效果:GrayscaleEffect

  • OpacityMask

四、样式与资源

1. 对象样式

  • 属性设置
  • 样式定义:Style
    • 设置指定对象的属性
    • 为什么需要Style?与属性指定有什么区别?感觉复杂了
  • 样式基本属性:TargetType、Setters、Key、BaseOn、Triggers
  • 样式触发器:根据用户行为,改变控件外观
    • Trigger、MultiTrigger、DataTrigger、MultiDataTrigger、EventTrigger
  • 访问级别:越接近对象,级别越高
  • 样式继承

2. 对象资源

  • 对象资源

    • 参数复用
    • 资源的定义
    • 使用方式
    • 资源统一设置:对象资源属性/向上搜索:APP、系统资源
    • C#对象与XAML的对应关系
  • 静态资源与动态资源

    • 静态:编译时确定
    • 动态:运行时确定
    • 使用区别:希望资源在运行过程中指定 使用动态;如果固定资源使用静态
    • 性能上首推静态
  • WPF 项目的资源管理

    • 常见资源类型:图片(SVG)、字体、音视频(Gif)(只能本地文件访问)、XAML文档(资源字典)、C#对象

    • 项目资源存储方式:

    • 资源访问:项目资源/本地资源

    • pack URL

      pack://application:,,,/程序集名称;[版本号;][公钥;]component/Images/logo.png
      pack://siteoforigin:,,,/Images/logo.png
      
    • 目的:重用

    • 字体资源的特殊处理过程

      • 字体打包/字体图标引用/字体合并
    • 动态资源的实时更新

      • 多语言切换/主题切换

五、控件模版

1. WPF控件的组成

  • 逻辑与呈现的分离
    • 控件模板旨在成为展示详细信息的自包含单元,对外部用户和对象(包括 Style 对象)不可见。 操作控件模板内容的唯一方法是在当前控件模板中。
  • 模板修改:显示区域的内容重组
    • Button模板Template:渲染对象、属性值的关联、触发器
    • 样式与模板
    • 模板干什么 ?在控件有效空间内进行布局
  • 基础控件如何显示的?

2. 模板中的基础控件

  • 基础元素的呈现:DrawingContext DrawRect
    • TextBlock
    • Border
    • OnRender
  • 控件模板解析
    • Button、Label、GroupBox、CheckBox、RadioButton、ToggleButton
    • TextBox、PasswordBox、TabControl/TabItem、TreeView/TreeViewItem、ListBox/ListBoxItem、Menu/MenuItem
    • ComboBox、ScrollViewer/ScrollBar、ProgressBar、Slider
    • Calendar、DatePicker、ListView、DataGrid
    • 模板的添加与修改
    • 默认模板:wpf\src\Microsoft.DotNet.Wpf\src\Themes
  • 基本控件的使用
    • 名称、基本作用表现、特征
    • 常用属性:尺寸、定位、显示内容
    • 常用事件:点击、输入
  • 控件的特殊属性
    • Style:用来统一设置控件属性的对象
    • Resources:资源管理,存放可共用的数据(变量、样式、模板)
    • Template:用来控制控件的呈现

六、依赖属性

1. 绑定关系

WPF的数据绑定关系:数据绑定是应用呈现数据并与数据交互提供了一种简单而一致的方法,在应用 UI 与其显示的数据之间建立连接的过程。

建立数据绑定关系的核心条件:

  • 依赖对象的依赖属性
  • 依赖附加属性(一个类型只是使用方式不同,有一点所附加的对象需要是依赖对象)

2. WPF依赖属性

依赖属性的意义与作用:

  • 核心模块
  • 内存共享,节省空间
  • 数据绑定、样式、模板、动画......
  • 如果没有依赖属性,这个框架就是一个控件框架,相当于 Winform

依赖属性的定义:

  • 基本过程:声明、注册、包装
  • 代码片段:propdp

依赖属性的主要参数与回调方法:

  • 默认值
  • 元数据:FrameworkPropertyMetadataOptions
  • 属性值变化回调、验证回调、强制回调

依赖属性的继承:

  • 允许元素树中的子元素从最近的父元素获取特定属性的值。
  • 由于父元素也可能通过属性值继承获得其属性值,因此系统可能递归回页面根。
public class DependencyPropertyStudy : Control
{
    public DependencyPropertyStudy()
    {
        // 基本三个参数:属性名称、属性类型、所属类型
        // 默认值一定要跟属性类型严格一致
        // 委托回调方法参数
        ValueProperty = DependencyProperty.Register(
            "Value",
            typeof(double),
            typeof(DependencyPropertyStudy),

            new PropertyMetadata(
                0.0,
                new PropertyChangedCallback(OnValueChanged),
                new CoerceValueCallback(OnValueCoerce)),

            new ValidateValueCallback(OnValueValidation)
            );

        //ValueProperty=DependencyProperty.Register
    }
    
    // 属性值变化回调:处理的是监听属性值的变化
    private void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // 如果被调用成功,这里就将知道对应的依赖值发了生变化
        (d as DependencyPropertyStudy).Refresh();
        //e.NewValue;
        //e.OldValue;
    }
    
    /// <param name="v">依赖属性所接收到的最新的值</param>
    /// <returns>对数据的判断结果,只有状态结果,True/False</returns>
    private static bool OnValueValidation(object v)
    {
        //if ((double)v > 2000)
        //    return false;
        return true;
    }
    
    // IP: 0-255
    private static object OnValueCoerce(DependencyObject d, object v)
    {
        if ((double)v > 255) return 255.0;
        if ((double)v < 0) return 0.0;
        return v;
    }
    
    // 利用普通属性对依赖属性的封装
	// 目的是为了在代码中方便访问,绑定过程不会用到
    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    // 依赖属性所在对象必须是依赖对象
    // 声明搞定 readonly:只能被赋值一次,不能被二次修改
    public static readonly DependencyProperty ValueProperty;
}

3. 依赖附加属性

依赖附加属性的意义与作用:

  • 给其它对象提供依赖属性功能
  • 有些对象的某些属性不能做绑定(PasswordBox)
  • 例子:布局控件做动态子项
  • 代码片段关键词:propa

依赖附加属性的定义:

  • 基本过程:声明、注册、包装
  • 依赖属性必须在依赖对象,附加属性不一定,关注的是被附加的对象是否是依赖对象

4. 示例(PasswordHelper)

public class PWHelper
{
    public static string GetPassword(DependencyObject obj)
    {
        return (string)obj.GetValue(PasswordProperty);
    }

    public static void SetPassword(DependencyObject obj, string value)
    {
        obj.SetValue(PasswordProperty, value);
    }
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PWHelper), new PropertyMetadata(""
            , new PropertyChangedCallback(OnPasswordChanged)));

    // 1、可以获取到对应的控件PasswordBox
    // 2、关联两个方向
    //    - 从绑定到控件的
    //    - 从控件到绑定(针对拿到的控件 进行PasswordChanged事件的绑定)
    /// <param name="dependencyObject">实附加的对象</param>
    /// <param name="e"></param>
    private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //var control = (d as PasswordBox);
        //if (control == null) return;

        if (d is PasswordBox control)
        {
            control.Password = e.NewValue.ToString();
        }
    }

    private static void Control_PasswordChanged(object sender, RoutedEventArgs e)
    {
        SetPassword((DependencyObject)sender, (sender as PasswordBox).Password);
    }

    public static object GetAttached(DependencyObject obj)
    {
        return (object)obj.GetValue(AttachedProperty);
    }

    public static void SetAttached(DependencyObject obj, object value)
    {
        obj.SetValue(AttachedProperty, value);
    }
    public static readonly DependencyProperty AttachedProperty =
        DependencyProperty.RegisterAttached("Attached", typeof(object),
            typeof(PWHelper), new PropertyMetadata(null, new PropertyChangedCallback(OnAttachedChanged)));

    private static void OnAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (d as PasswordBox);
        if (control == null) return;

        // 
        control.PasswordChanged -= Control_PasswordChanged;
        control.PasswordChanged += Control_PasswordChanged;
    }
}
<Grid>
    <!--不允许绑定的历史,
    桌面     SilverLight  银光  Web插件应用   Flash
    绑定的解决办法  就是依赖附加属性
    -->
    <PasswordBox Password="12345" PasswordChar="0" 
                 local:PWHelper.Password="123"
                 local:PWHelper.Attached="1"/>        
    <!--关于附加属性的使用-->
    <!--1、用来辅助布局(布局控件与子控件的关系)
           一般容器控件提供给子控件使用,比如Grid.Row
           使用方式:在容器控件中检查每个子项,从子项中获取这个附加属性的值   容器对象调用对应GetXXXX方法,传入被附加的对象
       -->

    <!--2、用来扩展绑定属性(单独应用对象功能)
           一般对接特定对象,比如PsswordBox
           使用方法:定义一个类,在类中定义相关附加属性,利用这些附加属性进行数据与控件属性的转接
    -->
</Grid>

七、数据绑定

1. 关于数据绑定

  • Binding
  • 绑定:描述的是一种关系,通过某种关系将多个事物联系在一起
  • 通常情况下,每个绑定具有四个组件
    • 绑定目标对象。
    • 目标属性。
    • 绑定源。
    • 指向绑定源中要使用的值的路径。
  • 数据绑定的典型用法是将服务器或本地配置数据放置到窗体或其他 UI 控件中。不论要绑定什么元素,也不论数据源是什么性质,每个绑定都始终遵循下图所示的模型。

2. 数据绑定中的数据源

  • 指定方式:
    • DataContext、Source、ElementName、RelativeSource
    • Path、XPath
  • 数据源类型:
    • 依赖对象做为数据源
    • 普通数据类型或集合类型做为数据源
    • 单个对象做为数据源
    • ADO.NET数据对象做为数据源
    • ObjectDataProvider做为数据源
    • Linq结果做为数据源 :List
    • XmlDataProvider做为数据源
    • 静态对象属性
  • 案例:
    • WPF 界面对象自适应,界面中有个控件,宽度希望是窗口的一半、三分之一、三分之二
    • 多数据源的处理

3. 集合绑定与数据模板

  • 集合数据源 ItemsSource
    • ItemsControl、ListBox、ListView
    • ListView、DataGrid
    • TreeView
    • 其他集合控件
  • 子项模板显示
  • 模板混合显示
    • 模板触发器
    • 模板选择器
  • 案例
    • 直方图
    • 集合数据多级联动(省市区)

4. 数据绑定对象中的辅助属性

  • Mode
  • UpdateSourceTrigger
  • Delay
  • StringFormat
  • Converter、ConverterParemeter(源与目标呈现数据类型不统一的时候)
  • FallbackValue:无法绑定的时候,显示一个默认值(XAML绑定失败窗口有错误)
  • TargetNullValue:数据源属性的值为null的时候目标属性中需要显示的信息
  • 验证与异常
    • ValidationRules
    • ValidatesOnDataErrors

4. 绑定模式下的数据验证

  • ValidationRules
    • ExceptionValidationRule
    • 自定义ValidationRule
  • IDataErrorInfo:配合Attribute进行各种数据校验

5. 数据绑定方式扩展

  • 多重绑定:MultiBinding
    • MultValueConverter
    • 案例:动态传值,参与绑定运算
  • 优先级绑定:PriorityBinding,允许一次绑定多个属性,在顺序上哪个先得到就显示哪个
  • 移除绑定
    • 目标属性赋值
    • BindingOperations

八、对象事件

1. 事件无处不在

  • 委托的使用、执行时机
  • 生命周期事件(App)
  • 路由事件:(鼠标、键盘、拖拽、触控事件、Stylus)
  • 行为:事件的封装
  • Main.exe 需要收参数 bat
  • 调试版本(更多 的日志输出)/生产版本(执行效率)

2. 生命周期

App生命周期

  • Startup、
  • Navigating、LoadCompleted、Navigated、NavigationFailed、NavigationProgress、NavigationStopped
  • SessionEnding
  • Activated、Deactivated
  • Exit
    • this.Close(); 关闭窗口,当APP里所有窗口都关闭的时候,App执行退出逻辑,执行Exit事件
    • Application.Current.Shutdown(); // 不管有多少窗口打开,退出当前App,执行Exit事件
    • System.Environment.Exit(0); 杀进程,不执行App的Exit事件
public App()
{
    // 生命周期事件   
    this.Startup += App_Startup;
    this.Exit += App_Exit;

    this.SessionEnding += App_SessionEnding;

    // Dispatcher   UI线程    未被处理的异常
    this.DispatcherUnhandledException += App_DispatcherUnhandledException;

    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}

3. 全局异常捕获

  • DispatcherUnhandledException
  • AppDomain.CurrentDomain.UnhandledException
  • TaskScheduler.UnobservedTaskException

4. 输入事件

  • 鼠标输入事件:FrameworkElement:UIElement:Visual
    • Click-》WPF的Windows消息处理与Winform不一样
    • MouseEnter、MouseLeave、
    • MouseDown、MouseUp、MouseMove
    • MouseLeftButtonDown、MouseLeftButtonUp、MouseRightButtonDown、MouseRightButtonUp、MouseDoubleClick
    • Mouse.Capture
  • 键盘输入事件
    • KeyDown、KeyUp、TextInput
    • TextChanged
  • 拖拽事件
    • Drop、DragLeave、DragOver、DragEnter 冒泡和隧道
  • 触控事件与笔势(Stylus开头的事件) 如果在触控设备中,鼠标事件时灵时不灵的时候,换Touch事件处理
    • Touch PreviewTouch Win7 Surface
    • 多点触控(Manipulation开头的事件),必须打开IsManipulationEnabled="True“
  • 自定义事件(普通事件)
    • 定义
    • 触发

5. 路由事件

  • 逻辑树与视觉树
  • 冒泡与隧道
    • MouseLeftButtonDown PreviewMouseLeftButtonDown
    • 逻辑处理逻辑:为什么需要
  • 自定义路由事件
    • 定义
    • 触发
    • 附加事件

/// 用于路由事件 
public class BaseClass : ContentControl
{
    // 路由事件的声明与注册
    // (冒泡事件)
    protected static readonly RoutedEvent TapEvent =
        EventManager.RegisterRoutedEvent(
            "Tap",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(BaseClass));

    public event RoutedEventHandler Tap
    {
        add => AddHandler(TapEvent, value);
        remove => RemoveHandler(TapEvent, value);
    }

    /// <summary>
    /// 隧道事件 
    /// </summary>
    public event RoutedEventHandler PreviewTap
    {
        add => AddHandler(PreviewTapEvent, value);
        remove => RemoveHandler(PreviewTapEvent, value);
    }

    public static readonly RoutedEvent PreviewTapEvent =
        EventManager.RegisterRoutedEvent(
            "PreviewTap",
            RoutingStrategy.Tunnel,
            typeof(RoutedEventHandler),
            typeof(BaseClass));
}

public class ParentClass : BaseClass
{ }

public class ChildClass : BaseClass
{
    public ChildClass()
    {
        // 场景,类里数据执行到某个时机的时候  ,触发上面的路由事件
        Task.Run(async () =>
        {
            await Task.Delay(2000);

            // 触发路由事件
            this.Dispatcher.Invoke(() =>
            {
                // 先Parent  后Child
                this.RaiseEvent(new RoutedEventArgs(PreviewTapEvent, this));

                // 先Child  后Parent
                this.RaiseEvent(new RoutedEventArgs(TapEvent, this));
            });
        });
    }
}
<Grid local:BaseClass.Tap="Grid_Tap" local:BaseClass.PreviewTap="Grid_PreviewTap">
    <local:ParentClass Tap="ParentClass_Tap" PreviewTap="ParentClass_PreviewTap">
        <local:ChildClass Tap="ChildClass_Tap" PreviewTap="ChildClass_PreviewTap"/>
    </local:ParentClass>
</Grid>
  • 页面交互案例
    • 鼠标事件:界面对象的移动控制
    • 拖拽事件:界面对象的动态生成
  • eventr.snippet
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
	<CodeSnippet Format="1.0.0">
		<Header>
			<Title>Route Event</Title>
			<Shortcut>eventr</Shortcut>
			<Description>生成路由事件</Description>
			<Author>Jovan</Author>
			<SnippetTypes>
				<SnippetType>Expansion</SnippetType>
			</SnippetTypes>
		</Header>
		<Snippet>
			<Declarations>
				<Literal>
					<ID>event_name</ID>
					<ToolTip>事件名称</ToolTip>
					<Default>MyEvent</Default>
				</Literal>
				<Literal>
					<ID>owner</ID>
					<ToolTip>所在对象</ToolTip>
					<Default>owner</Default>
				</Literal>
			</Declarations>
			<Code Language="csharp"><![CDATA[public event RoutedEventHandler $event_name$
        {
            add => AddHandler($event_name$Event, value);
            remove => RemoveHandler($event_name$Event, value);
        }
		
		public static readonly RoutedEvent $event_name$Event =
            EventManager.RegisterRoutedEvent(
                "$event_name$",
                RoutingStrategy.Bubble,
                typeof(RoutedEventHandler),
                typeof($owner$));

       
	$end$]]>
			</Code>
		</Snippet>
	</CodeSnippet>
</CodeSnippets>

6. 行为

  • 什么是行为

    • 行为并不是WPF中的核心的部分,是Expression Blend的设计特性。使用行为的地方,也是可以使用触发器取代的。

    • 事件封装

  • 行为的使用

// 创建一个行为类,用来封装事件逻辑:对象移动的事件 
public class DragMoveBehavior : Behavior<FrameworkElement>
{
    // 执行当前行为所依附的对象的事件挂载
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
        AssociatedObject.MouseMove += AssociatedObject_MouseMove;
        AssociatedObject.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
    }
    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
        AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
        AssociatedObject.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
    }

    private void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        is_obj_moving = false;

        Mouse.Capture(null);
    }

    private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    {
        if (is_obj_moving == false) return;

        //光标移动后,在Canvas上的当前位置


        Point p = e.GetPosition(AssociatedObject.Parent as Canvas);

        double offset_x = p.X - start_point.X;   // 偏移100
        double offset_y = p.Y - start_point.Y;   // 偏移50


        Canvas.SetLeft((UIElement)sender, start_x + offset_x);
        Canvas.SetTop((UIElement)sender, start_y + offset_y);
    }

    private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        // 获取光标在Canvas上的当前位置
        start_point = e.GetPosition(AssociatedObject.Parent as Canvas);

        start_x = Canvas.GetLeft((UIElement)sender);// 当前50
        start_y = Canvas.GetTop((UIElement)sender); // 当前50

        is_obj_moving = true;

        // 把对象绑定到光标上   两种处理
        // 1、
        Mouse.Capture((UIElement)sender);
    }
    private Point start_point;
    private double start_x, start_y;
    private bool is_obj_moving = false;
}
<Canvas>
    <Border Width="200" Height="50" Background="Orange" Canvas.Left="0" Canvas.Top="0">
        <b:Interaction.Behaviors>
            <local:DragMoveBehavior/>
        </b:Interaction.Behaviors>
    </Border>
    <Border Width="200" Height="50" Background="Green" Canvas.Left="200" Canvas.Top="50">
        <b:Interaction.Behaviors>
            <local:DragMoveBehavior/>
        </b:Interaction.Behaviors>
    </Border>
    <Label Content="Label" Width="100" Height="40" Background="Gray" 
           Canvas.Left="100" Canvas.Top="80">
        <b:Interaction.Behaviors>
            <local:DragMoveBehavior/>
        </b:Interaction.Behaviors>
    </Label>

    <!--目前所封装的Behavior最好不要用Button来测试-->
    <!--<Button Width="300" Height="300"/>-->
</Canvas>