原文網址:http://www.cnblogs.com/wangshenhe/archive/2012/05/14/2499159.html
前言:
很多时候我们需要在运行时,动态地改变控件的位置以及大小,以获得更好的布局。比如说实际项目中的可自定义的报表、可自定义的单据等诸如此类。它们有个特点就是允许客户或者二次开发人员设计它们需要的界面设置功能。
本人以前也做过可自定义系统,包括界面和功能,主要为了减少开发人员的工作量以及程序的灵活性和健壮性。
本篇主要讨论下,在运行时如何实现拖拉控件,达到改变控件位置与大小。功能将模拟VS设计界面时的拖拉功能。
(本篇暂不涉及多控件同时操作)
一、技术概述
其实实现运行时控件的拖拉并不难,主要是改变控件的Location与Size即可。动态调整时再捕获MouseDown、MouseMove及MouseUp事件来实时修改上述两个属性就可以实现。
二、功能规划
在此之前,我们先来看下.net设计界面,一旦选中某个控件时,将会出现如下图的边框:
之后就可以通过拖拉出现的边框改变其大小。而改变控件的位置,实际上是当鼠标点击在控件内部拖动时实现的。
所有本例也将功能分为两个部分实现,分别为控件内部拖动改变位置与控件边框拖拉改变大小。
三、具体实现
1.拖动控件改变位置
首先,新建一个项目,然后添加一个类,取名叫MoveControl,该类用来给控件挂载事件实现拖动。
接着在该类中添加字段currentControl,用来保存需要操作的控件,即通过构造函数传递的控件。
接着创建一方法--AddEvents,用来给当前的控件挂载事件。
代码如下:

1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Windows.Forms; 5 using System.Drawing; 6 7 namespace DragControl 8 { 9 public class MoveControl 10 { 11 #region Constructors 12 public MoveControl(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 /// <summary> 29 /// 挂载事件 30 /// </summary> 31 private void AddEvents() 32 { 33 currentControl.MouseClick += new MouseEventHandler(MouseClick); 34 currentControl.MouseDown += new MouseEventHandler(MouseDown); 35 currentControl.MouseMove += new MouseEventHandler(MouseMove); 36 currentControl.MouseUp += new MouseEventHandler(MouseUp); 37 } 38 #endregion 39 40 #region Events 41 /// <summary> 42 /// 鼠标单击事件:用来显示边框 43 /// </summary> 44 /// <param name="sender"></param> 45 /// <param name="e"></param> 46 void MouseClick(object sender, MouseEventArgs e) 47 { 48 } 49 50 /// <summary> 51 /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 52 /// </summary> 53 void MouseDown(object sender, MouseEventArgs e) 54 { 55 56 } 57 58 /// <summary> 59 /// 鼠标移动事件:让控件跟着鼠标移动 60 /// </summary> 61 void MouseMove(object sender, MouseEventArgs e) 62 { 63 } 64 65 /// <summary> 66 /// 鼠标弹起事件:让自定义的边框出现 67 /// </summary> 68 void MouseUp(object sender, MouseEventArgs e) 69 { 70 } 71 #endregion 72 } 73 }
接着我们需要实现MouseDown、MouseMove、MouseUp三个事件。
不过在此之前,我们必须要弄清楚,移动即表示坐标的改变,所以必定要有个起始坐标和终点坐标。
所以我们在MoveControl类中加入两个字段。
private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标
而且在开始拖动之前,我们肯定需要先单击一次控件。在MouseDown时获取当前光标的位置,保存到pPoint中。
(此处用Cursor获得坐标的好处,就是忽略掉容器的麻烦问题)
1 /// <summary> 2 /// 鼠标单击事件:用来显示边框 3 /// </summary> 4 void MouseClick(object sender, MouseEventArgs e) 5 { 6 pPoint = Cursor.Position; 7 }
接着便实现MouseMove的事件,当鼠标左键按下时,接着移动鼠标后,继续鼠标移动后的坐标,然后与MouseDown时记下的坐标相减,就得到鼠标的位移值,接着控件的Location加上该位移值即可,然后更新pPoint。
1 /// <summary> 2 /// 鼠标移动事件:让控件跟着鼠标移动 3 /// </summary> 4 void MouseMove(object sender, MouseEventArgs e) 5 { 6 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll 7 //当鼠标左键按下时才触发 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的完整代码如下:

1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Windows.Forms; 5 using System.Drawing; 6 7 namespace DragControl 8 { 9 public class MoveControl 10 { 11 #region Constructors 12 public MoveControl(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 /// <summary> 31 /// 挂载事件 32 /// </summary> 33 private void AddEvents() 34 { 35 currentControl.MouseDown += new MouseEventHandler(MouseDown); 36 currentControl.MouseMove += new MouseEventHandler(MouseMove); 37 currentControl.MouseUp += new MouseEventHandler(MouseUp); 38 } 39 40 /// <summary> 41 /// 绘制拖拉时的黑色边框 42 /// </summary> 43 public static void DrawDragBound(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(new Pen(Color.Black), ps); 52 } 53 #endregion 54 55 #region Events 56 /// <summary> 57 /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 58 /// </summary> 59 void MouseDown(object sender, MouseEventArgs e) 60 { 61 pPoint = Cursor.Position; 62 } 63 64 /// <summary> 65 /// 鼠标移动事件:让控件跟着鼠标移动 66 /// </summary> 67 void MouseMove(object sender, MouseEventArgs e) 68 { 69 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll 70 //当鼠标左键按下时才触发 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 /// <summary> 83 /// 鼠标弹起事件:让自定义的边框出现 84 /// </summary> 85 void MouseUp(object sender, MouseEventArgs e) 86 { 87 this.currentControl.Refresh(); 88 } 89 #endregion 90 } 91 }
下面我们来测试下拖动的功能。
创建一个Form窗体,可以再界面上添加你要测试的控件类型,此处我只用TextBox左下测试。在Load的中添加以下代码,将Form中的所有控件挂载上拖拉功能。
1 private void Form1_Load(object sender, EventArgs e) 2 { 3 foreach (Control ctrl in this.Controls) 4 { 5 new MoveControl(ctrl); 6 } 7 }
此时,有心人可能会发现VS中拖动控件时,将会出现黑色边框,而处于没有。
这也很简单,我们在MouseMove时加上如下代码即可。
1 /// <summary> 2 /// 绘制拖拉时的黑色边框 3 /// </summary> 4 public static void DrawDragBound(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(new Pen(Color.Black), ps); 13 } 14 15 16 /// <summary> 17 /// 鼠标移动事件:让控件跟着鼠标移动 18 /// </summary> 19 void MouseMove(object sender, MouseEventArgs e) 20 { 21 Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll 22 //当鼠标左键按下时才触发 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 /// <summary> 2 /// 鼠标弹起事件:让自定义的边框出现 3 /// </summary> 4 void MouseUp(object sender, MouseEventArgs e) 5 { 6 this.currentControl.Refresh(); 7 }