使用扩展方法对代码的行为进行封装的例子:封装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:在一个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,且可以挂接多个逻辑。

代码如下:

代码

 本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2010/11/10/1874054.html如需转载请自行联系原作者


xiaotie 集异璧实验室(GEBLAB)

相关文章
|
8月前
|
小程序 JavaScript 前端开发
小程序封装加载动画
在小程序的开发中,页面的加载过程可能会因为网络状况的不好或数据量的过大而显得非常缓慢,这时候加上一个加载动画就能有效的缓解用户的等待焦虑感。而对于应用的多个页面来说,使用全局加载动画可以提高用户体验,让应用显得更加美观和专业。本篇技术分享博客将为大家介绍在小程序中封装全局加载动画的具体实现步骤,帮助您提高小程序的用户体验。通过上述步骤,我们就完成了小程序中封装全局加载动画的具体实现方法。在实际开发中,我们可以根据实际需求对组件样式和方法进行调整和修改,以满足不同的开发需求。
185 0
|
9月前
|
前端开发
封装avalonia指定组件允许拖动的工具类
封装avalonia指定组件允许拖动的工具类
88 0
|
9月前
|
C#
Blazor嵌套传递
Blazor嵌套传递
40 0
Blazor嵌套传递
|
Java
Java面向对象(4)--封装和隐藏
Java面向对象(4)--封装和隐藏
106 0
Java面向对象(4)--封装和隐藏
|
前端开发 JavaScript
element组件的属性、事件和方法怎么使用
我们在使用element组件的时候,经常会使用到组件的属性、事件和方法,但对于第一次接触element组件的小白来说,由于没有代码示例,所以不知道怎么使用组件的属性、事件和方法是很常见的情况,所以本文将教会大家怎么去使用element组件的属性、事件和方法
294 0
element组件的属性、事件和方法怎么使用
|
小程序
小程序封装tab组件
小程序封装tab组件
|
JavaScript 前端开发
jQuery(四)动画、类数组对象操作、添加自定义函数、封装自定义插件
jQuery(四)动画、类数组对象操作、添加自定义函数、封装自定义插件
125 0
jQuery(四)动画、类数组对象操作、添加自定义函数、封装自定义插件
|
JavaScript 前端开发
前端学习之原生JS实现attr方法的封装
前端学习之原生JS实现attr方法的封装
415 0
|
JavaScript
vue中怎样实现弹出层动画效果?由上而下渐渐显示---封装成复用组件
vue中怎样实现弹出层动画效果?由上而下渐渐显示---封装成复用组件
194 0
vue中怎样实现弹出层动画效果?由上而下渐渐显示---封装成复用组件
|
前端开发 JavaScript
常用的前端Js方法封装
常用的前端Js方法封装