很多情况下,我们需要对界面上的元素进行拖动,用鼠标在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) private static void element_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) 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:在一个ScrollViewer[scroll]中有1副Image[imgMain],当这个图像过大时,需要支持拖动,拖动时,鼠标由箭头变成手的样子,拖动完毕再变回来。界面见下图:
<a href="http://images.cnblogs.com/cnblogs_com/xiaotie/WindowsLiveWriter/UIElement_EF06/image_4.png"></a>
代码如下:
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]),有的文字压着线了,需要手动拖好。当选中文字时,文字显示红色,拖动完毕,变成黑色。
<a href="http://images.cnblogs.com/cnblogs_com/xiaotie/WindowsLiveWriter/UIElement_EF06/image_6.png"></a>
相关代码为:
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); }, tb.Foreground = new SolidColorBrush(Colors.Red); 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,且可以挂接多个逻辑。
本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2010/11/10/1874054.html如需转载请自行联系原作者
xiaotie 集异璧实验室(GEBLAB)