• 使用扩展方法对代码的行为进行封装的例子:封装UIElement的“拖动”


    很多情况下,我们需要对界面上的元素进行拖动,用鼠标在VS中biaji,biaji,biaji,点几个事件,然后再写出一堆代码,浪费时间不说,由IDE自动生成的那些代码实在是太难看,影响心情。本文使用扩展方法,对于这类行为需要进行封装,以使代码更简单简洁。

    封装原则如下:

    (1)要简单,最好是一行代码就搞定;

    (2)要强大,能用于尽量多的类;

    (3)要灵活,可适用于尽量多的场景。

    在本文的末尾添加了修改版,修改版代码更简洁,操作更简单,且可以设置多个拖动逻辑。

    ====

    设计下面的扩展方法原型:

    public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)

    element 是拖动的界面元素,relativeTo 是参照物,moveCallback 是移动过程中的回调方法。DraggableContext 是一个类,包装了调用方所需要的信息。beforeDragCallback 是拖动前的处理,假设要拖动一个TextBlock,拖动前可以将它改变颜色,以示醒目。afterDragCallback 是拖动结束后的处理。

    DraggableContext 的代码为:

    public class DraggableContext
    {
        public DependencyObject Owner { get; private set; }
        public IInputElement RelativeTo { get; private set; }
        public Point StartPoint { get; internal set; }
        public Point EndPoint { get; internal set; }

        public Point Offset
        {
            get { return new Point { X = EndPoint.X - StartPoint.X, Y = EndPoint.Y - StartPoint.Y }; }
        }

        private Boolean _dragging;

        public Boolean Dragging
        {
            get { return _dragging; }
            internal set
            {
                if (value != _dragging)
                {
                    _dragging = value;
                    if (value == true)
                    {
                        if (BeforeDragCallback != null)
                            BeforeDragCallback(this);
                    }
                    else
                    {
                        if (AfterDragCallback != null)
                            AfterDragCallback(this);
                    }
                }
            }
        }

        public Action<DraggableContext> MoveCallback { get; private set; }

        public Action<DraggableContext> BeforeDragCallback { get; private set; }

        public Action<DraggableContext> AfterDragCallback { get; private set; }

        public DraggableContext(DependencyObject owner, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
        {
            Owner = owner;
            RelativeTo = relativeTo;
            MoveCallback = moveCallback;
            BeforeDragCallback = beforeDragCallback;
            AfterDragCallback = afterDragCallback;
        }

        public override int GetHashCode()
        {
            return Owner.GetHashCode();
        }
    }

    然后,还需要一个Dictionary,来储存所有的调用者:

      private static Dictionary<Object, DraggableContext> _dicDragContext = new Dictionary<Object, DraggableContext>();

    然后,是对拖动逻辑的实现:

    public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
    {
        if (element == null) throw new ArgumentNullException("element");

        _dicDragContext[element] = new DraggableContext(element, relativeTo, moveCallback, beforeDragCallback, afterDragCallback);
        element.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);
        element.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);
        element.MouseLeave += new System.Windows.Input.MouseEventHandler(element_MouseLeave);
        element.MouseMove += new System.Windows.Input.MouseEventHandler(element_MouseMove);
    }

    private static void element_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        _dicDragContext[sender].Dragging = false;
    }

    private static void element_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        DraggableContext ctx = _dicDragContext[sender];
        ctx.Dragging = true;
        ctx.StartPoint = e.GetPosition(ctx.RelativeTo);
        ctx.EndPoint = ctx.StartPoint;
    }

    private static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
    {
        _dicDragContext[sender].Dragging = false;
    }

    private static void element_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    {
        DraggableContext ctx = _dicDragContext[sender];
        if (ctx.Dragging == true)
        {
            ctx.Dragging = true;
            ctx.EndPoint = e.GetPosition(ctx.RelativeTo);
            if (ctx.MoveCallback != null)
            {
                ctx.MoveCallback(ctx);
            }
            ctx.StartPoint = ctx.EndPoint;
        }
    }

    最后,还需要提供一个扩展方法清除对对象和事件的引用,以避免出现内存泄漏。这个方法需要显示调用。

    public static void UnsetDraggable(this UIElement element)
    {
        element.MouseLeftButtonDown -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);
        element.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);
        element.MouseLeave -= new System.Windows.Input.MouseEventHandler(element_MouseLeave);
        element.MouseMove -= new System.Windows.Input.MouseEventHandler(element_MouseMove);

        if (_dicDragContext.ContainsKey(element)) _dicDragContext.Remove(element);
    }

    完整代码如下:

    代码
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Windows;
      6 using System.Windows.Controls;
      7 
      8 namespace Orc.Util
      9 {
     10     public class DraggableContext
     11     {
     12         public DependencyObject Owner { getprivate set; }
     13         public IInputElement RelativeTo { getprivate set; }
     14         public Point StartPoint { getinternal set; }
     15         public Point EndPoint { getinternal set; }
     16 
     17         public Point Offset
     18         {
     19             get { return new Point { X = EndPoint.X - StartPoint.X, Y = EndPoint.Y - StartPoint.Y }; }
     20         }
     21 
     22         private Boolean _dragging;
     23 
     24         public Boolean Dragging
     25         {
     26             get { return _dragging; }
     27             internal set
     28             {
     29                 if (value != _dragging)
     30                 {
     31                     _dragging = value;
     32                     if (value == true)
     33                     {
     34                         if (BeforeDragCallback != null)
     35                             BeforeDragCallback(this);
     36                     }
     37                     else
     38                     {
     39                         if (AfterDragCallback != null)
     40                             AfterDragCallback(this);
     41                     }
     42                 }
     43             }
     44         }
     45 
     46         public Action<DraggableContext> MoveCallback { getprivate set; }
     47 
     48         public Action<DraggableContext> BeforeDragCallback { getprivate set; }
     49 
     50         public Action<DraggableContext> AfterDragCallback { getprivate set; }
     51 
     52         public DraggableContext(DependencyObject owner, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
     53         {
     54             Owner = owner;
     55             RelativeTo = relativeTo;
     56             MoveCallback = moveCallback;
     57             BeforeDragCallback = beforeDragCallback;
     58             AfterDragCallback = afterDragCallback;
     59         }
     60 
     61         public override int GetHashCode()
     62         {
     63             return Owner.GetHashCode();
     64         }
     65 
     66         public static void MoveOnCanvas(DraggableContext ctx)
     67         {
     68             Canvas cvs = ctx.RelativeTo as Canvas;
     69             if (cvs == nullthrow new NotSupportedException("RelativeTo 必须是 Canvas");
     70             ctx.Owner.SetValue(Canvas.TopProperty, (double)(ctx.Owner.GetValue(Canvas.TopProperty)) + ctx.Offset.X);
     71             ctx.Owner.SetValue(Canvas.LeftProperty, (double)(ctx.Owner.GetValue(Canvas.LeftProperty)) + ctx.Offset.Y);
     72         }
     73     }
     74 
     75     public static class ClassHelper
     76     {
     77         private static Dictionary<Object, DraggableContext> _dicDragContext = new Dictionary<Object, DraggableContext>();
     78 
     79         public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
     80         {
     81             if (element == nullthrow new ArgumentNullException("element");
     82 
     83             _dicDragContext[element] = new DraggableContext(element, relativeTo, moveCallback, beforeDragCallback, afterDragCallback);
     84             element.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);
     85             element.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);
     86             element.MouseLeave += new System.Windows.Input.MouseEventHandler(element_MouseLeave);
     87             element.MouseMove += new System.Windows.Input.MouseEventHandler(element_MouseMove);
     88         }
     89 
     90         private static void element_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
     91         {
     92             _dicDragContext[sender].Dragging = false;
     93         }
     94 
     95         private static void element_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
     96         {
     97             DraggableContext ctx = _dicDragContext[sender];
     98             ctx.Dragging = true;
     99             ctx.StartPoint = e.GetPosition(ctx.RelativeTo);
    100             ctx.EndPoint = ctx.StartPoint;
    101         }
    102 
    103         private static void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
    104         {
    105             _dicDragContext[sender].Dragging = false;
    106         }
    107 
    108         private static void element_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    109         {
    110             DraggableContext ctx = _dicDragContext[sender];
    111             if (ctx.Dragging == true)
    112             {
    113                 ctx.Dragging = true;
    114                 ctx.EndPoint = e.GetPosition(ctx.RelativeTo);
    115                 if (ctx.MoveCallback != null)
    116                 {
    117                     ctx.MoveCallback(ctx);
    118                 }
    119                 ctx.StartPoint = ctx.EndPoint;
    120             }
    121         }
    122 
    123         public static void UnsetDraggable(this UIElement element)
    124         {
    125             element.MouseLeftButtonDown -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonDown);
    126             element.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(element_MouseLeftButtonUp);
    127             element.MouseLeave -= new System.Windows.Input.MouseEventHandler(element_MouseLeave);
    128             element.MouseMove -= new System.Windows.Input.MouseEventHandler(element_MouseMove);
    129 
    130             if (_dicDragContext.ContainsKey(element)) _dicDragContext.Remove(element);
    131         }
    132     }
    133 }
    134 

    ====

    下面,通过两个案例,看一下这样的封装能带来什么好处。

    案例1:在一个ScrollViewer[scroll]中有1副Image[imgMain],当这个图像过大时,需要支持拖动,拖动时,鼠标由箭头变成手的样子,拖动完毕再变回来。界面见下图:

    image

    代码如下:

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        this.imgMain.SetDraggable(this, this.Scroll, UseHandCursor, UseArrowCursor);
    }

    private void UseHandCursor(DraggableContext ctx)
    {
        (ctx.Owner as FrameworkElement).Cursor = Cursors.Hand;
    }

    private void UseArrowCursor(DraggableContext ctx)
    {
        (ctx.Owner as FrameworkElement).Cursor = Cursors.Arrow;
    }

    private void Scroll(DraggableContext ctx)
    {
        this.scroll.ScrollToHorizontalOffset(this.scroll.HorizontalOffset - ctx.Offset.X);
        this.scroll.ScrollToVerticalOffset(this.scroll.VerticalOffset - ctx.Offset.Y);
    }

    如果使用 Lambda 表达式,则更简单。

    案例2:还是和上面类似场景,在一个Canvas[cvsFont]上有很多文字(TextBlock[tb]),有的文字压着线了,需要手动拖好。当选中文字时,文字显示红色,拖动完毕,变成黑色。

    image

    相关代码为:

    tb.SetDraggable(this.cvsFont,
                        (DraggableContext ctx) =>
                            {
                                FontContext pos = tb.DataContext as FontContext;
                                pos.X += ctx.Offset.X / ImageScale;
                                pos.Y += ctx.Offset.Y / ImageScale;
                                tb.SetValue(Canvas.TopProperty, pos.Y * ImageScale);
                                tb.SetValue(Canvas.LeftProperty, pos.X * ImageScale);
                            },
                        (DraggableContext ctx) =>
                            {
                                tb.Foreground = new SolidColorBrush(Colors.Red);
                            },
                        (DraggableContext ctx) =>
                            {
                                tb.Foreground = new SolidColorBrush(Colors.Black);
                            }
                        );

    FontContext 存储了当缩放比为1时,tb 相对 cvsFont 的位置。这些TextBlock的生命周期比较短,它们排队领盒饭时,需要Unset一下:

    tb.UnsetDraggable();

    ====

    可以将常用的场景封装成方法,比如说,在DraggableContext类中添加下面的回调方法,将元素在Canvas上移动的逻辑抽象出来:

    public static void MoveOnCanvas(DraggableContext ctx)
    {
        Canvas cvs = ctx.RelativeTo as Canvas;
        if (cvs == null) throw new NotSupportedException("RelativeTo 必须是 Canvas");
        ctx.Owner.SetValue(Canvas.TopProperty, (double)(ctx.Owner.GetValue(Canvas.TopProperty)) + ctx.Offset.X);
        ctx.Owner.SetValue(Canvas.LeftProperty, (double)(ctx.Owner.GetValue(Canvas.LeftProperty)) + ctx.Offset.Y);
    }

    如果一个元素放置在Canvas上,且只需要支持拖动,不需要其它的逻辑,则一句话就搞定了:

    xxx.SetDraggable(cvsXXX,DraggableContext.MoveOnCanvas);

    ====

    2010年11月13日对代码进行修改,修改后的代码更简洁,使用更简单,不用手动Unset,且可以挂接多个逻辑。

    代码如下:

    代码
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Windows;
      6 using System.Windows.Controls;
      7 
      8 namespace Orc.Util
      9 {
     10     public class DraggableContext
     11     {
     12         public DependencyObject Owner { getprivate set; }
     13         public IInputElement RelativeTo { getprivate set; }
     14         public Point StartPoint { getinternal set; }
     15         public Point EndPoint { getinternal set; }
     16 
     17         public Point Offset
     18         {
     19             get { return new Point { X = EndPoint.X - StartPoint.X, Y = EndPoint.Y - StartPoint.Y }; }
     20         }
     21 
     22         private Boolean _dragging;
     23 
     24         public Boolean Dragging
     25         {
     26             get { return _dragging; }
     27             internal set
     28             {
     29                 if (value != _dragging)
     30                 {
     31                     _dragging = value;
     32                     if (value == true)
     33                     {
     34                         if (BeforeDragCallback != null)
     35                             BeforeDragCallback(this);
     36                     }
     37                     else
     38                     {
     39                         if (AfterDragCallback != null)
     40                             AfterDragCallback(this);
     41                     }
     42                 }
     43             }
     44         }
     45 
     46         internal void element_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
     47         {
     48             this.Dragging = false;
     49         }
     50 
     51         internal void element_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
     52         {
     53             this.Dragging = true;
     54             this.StartPoint = e.GetPosition(this.RelativeTo);
     55             this.EndPoint = this.StartPoint;
     56         }
     57 
     58         internal void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
     59         {
     60             this.Dragging = false;
     61         }
     62 
     63         internal void element_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
     64         {
     65             if (this.Dragging == true)
     66             {
     67                 this.Dragging = true;
     68                 this.EndPoint = e.GetPosition(this.RelativeTo);
     69                 if (this.MoveCallback != null)
     70                 {
     71                     this.MoveCallback(this);
     72                 }
     73                 this.StartPoint = this.EndPoint;
     74             }
     75         }
     76 
     77         public Action<DraggableContext> MoveCallback { getprivate set; }
     78 
     79         public Action<DraggableContext> BeforeDragCallback { getprivate set; }
     80 
     81         public Action<DraggableContext> AfterDragCallback { getprivate set; }
     82 
     83         public DraggableContext(DependencyObject owner, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
     84         {
     85             Owner = owner;
     86             RelativeTo = relativeTo;
     87             MoveCallback = moveCallback;
     88             BeforeDragCallback = beforeDragCallback;
     89             AfterDragCallback = afterDragCallback;
     90         }
     91 
     92         public override int GetHashCode()
     93         {
     94             return Owner.GetHashCode();
     95         }
     96 
     97         public static void MoveOnCanvas(DraggableContext ctx)
     98         {
     99             Canvas cvs = ctx.RelativeTo as Canvas;
    100             if (cvs == nullthrow new NotSupportedException("RelativeTo 必须是 Canvas");
    101             ctx.Owner.SetValue(Canvas.TopProperty, (double)(ctx.Owner.GetValue(Canvas.TopProperty)) + ctx.Offset.X);
    102             ctx.Owner.SetValue(Canvas.LeftProperty, (double)(ctx.Owner.GetValue(Canvas.LeftProperty)) + ctx.Offset.Y);
    103         }
    104     }
    105 
    106     public static class ClassHelper
    107     {
    108         public static void SetDraggable(this UIElement element, IInputElement relativeTo, Action<DraggableContext> moveCallback = null, Action<DraggableContext> beforeDragCallback = null, Action<DraggableContext> afterDragCallback = null)
    109         {
    110             if (element == nullthrow new ArgumentNullException("element");
    111             DraggableContext ctx = new DraggableContext(element, relativeTo, moveCallback, beforeDragCallback, afterDragCallback);
    112             element.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(ctx.element_MouseLeftButtonDown);
    113             element.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(ctx.element_MouseLeftButtonUp);
    114             element.MouseLeave += new System.Windows.Input.MouseEventHandler(ctx.element_MouseLeave);
    115             element.MouseMove += new System.Windows.Input.MouseEventHandler(ctx.element_MouseMove);
    116         }
    117     }
    118 }
  • 相关阅读:
    Linux下JDK安装位置
    Oracle中的User与Schema
    MFC中不同窗口间的切换
    MFC 动态的画线 画当前的线是清除上一次画的线
    VC ADO连接ACCESS步骤及错误处理
    虚继承和虚函数继承
    虚拟机无法连接连接U盘
    未能找到类型或命名空间
    VS2008 重构 封装字段 出错 无法使用
    vs2010 Visual Studio 遇到了异常。这可能是由某个扩展导致的
  • 原文地址:https://www.cnblogs.com/xiaotie/p/1874054.html
Copyright © 2020-2023  润新知