使用扩展方法对代码的行为进行封装的例子:封装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 }

 

 

posted @ 2010-11-10 17:22  xiaotie  阅读(2349)  评论(6编辑  收藏  举报