很多情况下,我们需要对界面上的元素进行拖动,用鼠标在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 {
get
;
private
set
; }
13
public
IInputElement RelativeTo {
get
;
private
set
; }
14
public
Point StartPoint {
get
;
internal
set
; }
15
public
Point EndPoint {
get
;
internal
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 {
get
;
private
set
; }
47
48
public
Action
<
DraggableContext
>
BeforeDragCallback {
get
;
private
set
; }
49
50
public
Action
<
DraggableContext
>
AfterDragCallback {
get
;
private
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
==
null
)
throw
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
==
null
)
throw
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],当这个图像过大时,需要支持拖动,拖动时,鼠标由箭头变成手的样子,拖动完毕再变回来。界面见下图:
代码如下:
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]),有的文字压着线了,需要手动拖好。当选中文字时,文字显示红色,拖动完毕,变成黑色。
相关代码为:
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 {
get
;
private
set
; }
13
public
IInputElement RelativeTo {
get
;
private
set
; }
14
public
Point StartPoint {
get
;
internal
set
; }
15
public
Point EndPoint {
get
;
internal
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 {
get
;
private
set
; }
78
79
public
Action
<
DraggableContext
>
BeforeDragCallback {
get
;
private
set
; }
80
81
public
Action
<
DraggableContext
>
AfterDragCallback {
get
;
private
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
==
null
)
throw
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
==
null
)
throw
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
}