前言:
很多时候我们需要在运行时,动态地改变控件的位置以及大小,以获得更好的布局。比如说实际项目中的可自定义的报表、可自定义的单据等诸如此类。它们有个特点就是允许客户或者二次开发人员设计它们需要的界面设置功能。
本人以前也做过可自定义系统,包括界面和功能,主要为了减少开发人员的工作量以及程序的灵活性和健壮性。
本篇主要讨论下,在运行时如何实现拖拉控件,达到改变控件位置与大小。功能将模拟VS设计界面时的拖拉功能。
(本篇暂不涉及多控件同时操作)
一、技术概述
其实实现运行时控件的拖拉并不难,主要是改变控件的Location与Size即可。动态调整时再捕获MouseDown、MouseMove及MouseUp事件来实时修改上述两个属性就可以实现。
二、功能规划
在此之前,我们先来看下.net设计界面,一旦选中某个控件时,将会出现如下图的边框:
之后就可以通过拖拉出现的边框改变其大小。而改变控件的位置,实际上是当鼠标点击在控件内部拖动时实现的。
所有本例也将功能分为两个部分实现,分别为控件内部拖动改变位置与控件边框拖拉改变大小。
三、具体实现
1.拖动控件改变位置
首先,新建一个项目,然后添加一个类,取名叫MoveControl,该类用来给控件挂载事件实现拖动。
接着在该类中添加字段currentControl,用来保存需要操作的控件,即通过构造函数传递的控件。
接着创建一方法--AddEvents,用来给当前的控件挂载事件。
代码如下:
DragControl
1 usingSystem;2 usingSystem.Collections.Generic;3 usingSystem.Text;4 usingSystem.Windows.Forms;5 usingSystem.Drawing;6
7 namespaceDragControl8 {9 public classMoveControl10 {11 #region Constructors
12 publicMoveControl(Control ctrl)13 {14 currentControl =ctrl;15 AddEvents();16 }17 #endregion
18
19 #region Fields
20 private Control currentControl; //传入的控件
21 #endregion
22
23 #region Properties
24
25 #endregion
26
27 #region Methods
28 ///
29 ///挂载事件30 ///
31 private voidAddEvents()32 {33 currentControl.MouseClick += newMouseEventHandler(MouseClick);34 currentControl.MouseDown += newMouseEventHandler(MouseDown);35 currentControl.MouseMove += newMouseEventHandler(MouseMove);36 currentControl.MouseUp += newMouseEventHandler(MouseUp);37 }38 #endregion
39
40 #region Events
41 ///
42 ///鼠标单击事件:用来显示边框43 ///
44 ///
45 ///
46 void MouseClick(objectsender, MouseEventArgs e)47 {48 }49
50 ///
51 ///鼠标按下事件:记录当前鼠标相对窗体的坐标52 ///
53 void MouseDown(objectsender, MouseEventArgs e)54 {55
56 }57
58 ///
59 ///鼠标移动事件:让控件跟着鼠标移动60 ///
61 void MouseMove(objectsender, MouseEventArgs e)62 {63 }64
65 ///
66 ///鼠标弹起事件:让自定义的边框出现67 ///
68 void MouseUp(objectsender, MouseEventArgs e)69 {70 }71 #endregion
72 }73 }
接着我们需要实现MouseDown、MouseMove、MouseUp三个事件。
不过在此之前,我们必须要弄清楚,移动即表示坐标的改变,所以必定要有个起始坐标和终点坐标。
所以我们在MoveControl类中加入两个字段。
private Point pPoint; //上个鼠标坐标
private Point cPoint; //当前鼠标坐标
而且在开始拖动之前,我们肯定需要先单击一次控件。在MouseDown时获取当前光标的位置,保存到pPoint中。
(此处用Cursor获得坐标的好处,就是忽略掉容器的麻烦问题)
1 ///
2 ///鼠标单击事件:用来显示边框3 ///
4 void MouseClick(objectsender, MouseEventArgs e)5 {6 pPoint =Cursor.Position;7 }
接着便实现MouseMove的事件,当鼠标左键按下时,接着移动鼠标后,继续鼠标移动后的坐标,然后与MouseDown时记下的坐标相减,就得到鼠标的位移值,接着控件的Location加上该位移值即可,然后更新pPoint。
1 ///
2 ///鼠标移动事件:让控件跟着鼠标移动3 ///
4 void MouseMove(objectsender, MouseEventArgs e)5 {6 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll7 //当鼠标左键按下时才触发
8 if (e.Button ==MouseButtons.Left)9 {10 cPoint = Cursor.Position; //获得当前鼠标位置
11 int x = cPoint.X -pPoint.X;12 int y = cPoint.Y -pPoint.Y;13 currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y +y);14 pPoint =cPoint;15 }16 }
由于此时还没涉及到边框,所以MouseUp暂时不用处理。至此拖动的基本功能已经实现!
目前MoveControl的完整代码如下:
MoveControl
1 usingSystem;2 usingSystem.Collections.Generic;3 usingSystem.Text;4 usingSystem.Windows.Forms;5 usingSystem.Drawing;6
7 namespaceDragControl8 {9 public classMoveControl10 {11 #region Constructors
12 publicMoveControl(Control ctrl)13 {14 currentControl =ctrl;15 AddEvents();16 }17 #endregion
18
19 #region Fields
20 private Control currentControl; //传入的控件
21 private Point pPoint; //上个鼠标坐标
22 private Point cPoint; //当前鼠标坐标
23 #endregion
24
25 #region Properties
26
27 #endregion
28
29 #region Methods
30 ///
31 ///挂载事件32 ///
33 private voidAddEvents()34 {35 currentControl.MouseDown += newMouseEventHandler(MouseDown);36 currentControl.MouseMove += newMouseEventHandler(MouseMove);37 currentControl.MouseUp += newMouseEventHandler(MouseUp);38 }39
40 ///
41 ///绘制拖拉时的黑色边框42 ///
43 public static voidDrawDragBound(Control ctrl)44 {45 ctrl.Refresh();46 Graphics g =ctrl.CreateGraphics();47 int width =ctrl.Width;48 int height =ctrl.Height;49 Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0),50 new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)};51 g.DrawLines(newPen(Color.Black), ps);52 }53 #endregion
54
55 #region Events
56 ///
57 ///鼠标按下事件:记录当前鼠标相对窗体的坐标58 ///
59 void MouseDown(objectsender, MouseEventArgs e)60 {61 pPoint =Cursor.Position;62 }63
64 ///
65 ///鼠标移动事件:让控件跟着鼠标移动66 ///
67 void MouseMove(objectsender, MouseEventArgs e)68 {69 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll70 //当鼠标左键按下时才触发
71 if (e.Button ==MouseButtons.Left)72 {73 MoveControl.DrawDragBound(this.currentControl);74 cPoint = Cursor.Position; //获得当前鼠标位置
75 int x = cPoint.X -pPoint.X;76 int y = cPoint.Y -pPoint.Y;77 currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y +y);78 pPoint =cPoint;79 }80 }81
82 ///
83 ///鼠标弹起事件:让自定义的边框出现84 ///
85 void MouseUp(objectsender, MouseEventArgs e)86 {87 this.currentControl.Refresh();88 }89 #endregion
90 }91 }
下面我们来测试下拖动的功能。
创建一个Form窗体,可以再界面上添加你要测试的控件类型,此处我只用TextBox左下测试。在Load的中添加以下代码,将Form中的所有控件挂载上拖拉功能。
1 private void Form1_Load(objectsender, EventArgs e)2 {3 foreach (Control ctrl in this.Controls)4 {5 newMoveControl(ctrl);6 }7 }
此时,有心人可能会发现VS中拖动控件时,将会出现黑色边框,而处于没有。
这也很简单,我们在MouseMove时加上如下代码即可。
1 ///
2 ///绘制拖拉时的黑色边框3 ///
4 public static voidDrawDragBound(Control ctrl)5 {6 ctrl.Refresh();7 Graphics g =ctrl.CreateGraphics();8 int width =ctrl.Width;9 int height =ctrl.Height;10 Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0),11 new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)};12 g.DrawLines(newPen(Color.Black), ps);13 }14
15
16 ///
17 ///鼠标移动事件:让控件跟着鼠标移动18 ///
19 void MouseMove(objectsender, MouseEventArgs e)20 {21 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll22 //当鼠标左键按下时才触发
23 if (e.Button ==MouseButtons.Left)24 {25 MoveControl.DrawDragBound(this.currentControl);26 cPoint = Cursor.Position; //获得当前鼠标位置
27 int x = cPoint.X -pPoint.X;28 int y = cPoint.Y -pPoint.Y;29 currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y +y);30 pPoint =cPoint;31 }32 }
同时要在MoveUp的时候,刷新一下自己,让黑色边框消失掉!
1 ///
2 ///鼠标弹起事件:让自定义的边框出现3 ///
4 void MouseUp(objectsender, MouseEventArgs e)5 {6 this.currentControl.Refresh();7 }
接着用没有边框的控件测试下就会很明显。如下图所示:
2.通过边框拖拉控件改变大小
此处的主要思路为:点击控件的时候,创建一个自定义的用户控件,该用户控件响应区域就是传入控件的边框区域,同时给它画上虚线与8个小圆圈。
第一、创建用户控件--FrameControl(边框控件),然后增加一个字段用来保存传入的控件,还有加载事件,此处类同前面的MoveControl。
FrameControl
1 usingSystem;2 usingSystem.Collections.Generic;3 usi