前言:
很多时候我们需要在运行时,动态地改变控件的位置以及大小,以获得更好的布局。比如说实际项目中的可自定义的报表、可自定义的单据等诸如此类。它们有个特点就是允许客户或者二次开发人员设计它们需要的界面设置功能。
本人以前也做过可自定义系统,包括界面和功能,主要为了减少开发人员的工作量以及程序的灵活性和健壮性。
本篇主要讨论下,在运行时如何实现拖拉控件,达到改变控件位置与大小。功能将模拟VS设计界面时的拖拉功能。
(本篇暂不涉及多控件同时操作)
一、技术概述
其实实现运行时控件的拖拉并不难,主要是改变控件的Location与Size即可。动态调整时再捕获MouseDown、MouseMove及MouseUp事件来实时修改上述两个属性就可以实现。
二、功能规划
在此之前,我们先来看下.net设计界面,一旦选中某个控件时,将会出现如下图的边框:
之后就可以通过拖拉出现的边框改变其大小。而改变控件的位置,实际上是当鼠标点击在控件内部拖动时实现的。
所有本例也将功能分为两个部分实现,分别为控件内部拖动改变位置与控件边框拖拉改变大小。
三、具体实现
1.拖动控件改变位置
首先,新建一个项目,然后添加一个类,取名叫MoveControl,该类用来给控件挂载事件实现拖动。
接着在该类中添加字段currentControl,用来保存需要操作的控件,即通过构造函数传递的控件。
接着创建一方法--AddEvents,用来给当前的控件挂载事件。
代码如下:
DragControl
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; namespace DragControl { public class MoveControl { #region Constructors public MoveControl(Control ctrl) { currentControl = ctrl; AddEvents(); } #endregion #region Fields private Control currentControl; //传入的控件 #endregion #region Properties #endregion #region Methods /// <summary> /// 挂载事件 /// </summary> private void AddEvents() { currentControl.MouseClick += new MouseEventHandler(MouseClick); currentControl.MouseDown += new MouseEventHandler(MouseDown); currentControl.MouseMove += new MouseEventHandler(MouseMove); currentControl.MouseUp += new MouseEventHandler(MouseUp); } #endregion #region Events /// <summary> /// 鼠标单击事件:用来显示边框 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void MouseClick(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void MouseDown(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void MouseUp(object sender, MouseEventArgs e) { } #endregion } }
接着我们需要实现MouseDown、MouseMove、MouseUp三个事件。
不过在此之前,我们必须要弄清楚,移动即表示坐标的改变,所以必定要有个起始坐标和终点坐标。
所以我们在MoveControl类中加入两个字段。
private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标
而且在开始拖动之前,我们肯定需要先单击一次控件。在MouseDown时获取当前光标的位置,保存到pPoint中。
(此处用Cursor获得坐标的好处,就是忽略掉容器的麻烦问题)
/// <summary> /// 鼠标单击事件:用来显示边框 /// </summary> void MouseClick(object sender, MouseEventArgs e) { pPoint = Cursor.Position; }
接着便实现MouseMove的事件,当鼠标左键按下时,接着移动鼠标后,继续鼠标移动后的坐标,然后与MouseDown时记下的坐标相减,就得到鼠标的位移值,接着控件的Location加上该位移值即可,然后更新pPoint。
/// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll //当鼠标左键按下时才触发 if (e.Button == MouseButtons.Left) { cPoint = Cursor.Position; //获得当前鼠标位置 int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y + y); pPoint = cPoint; } }
由于此时还没涉及到边框,所以MouseUp暂时不用处理。至此拖动的基本功能已经实现!
目前MoveControl的完整代码如下:
MoveControl
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; namespace DragControl { public class MoveControl { #region Constructors public MoveControl(Control ctrl) { currentControl = ctrl; AddEvents(); } #endregion #region Fields private Control currentControl; //传入的控件 private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标 #endregion #region Properties #endregion #region Methods /// <summary> /// 挂载事件 /// </summary> private void AddEvents() { currentControl.MouseDown += new MouseEventHandler(MouseDown); currentControl.MouseMove += new MouseEventHandler(MouseMove); currentControl.MouseUp += new MouseEventHandler(MouseUp); } /// <summary> /// 绘制拖拉时的黑色边框 /// </summary> public static void DrawDragBound(Control ctrl) { ctrl.Refresh(); Graphics g = ctrl.CreateGraphics(); int width = ctrl.Width; int height = ctrl.Height; Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0), new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)}; g.DrawLines(new Pen(Color.Black), ps); } #endregion #region Events /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void MouseDown(object sender, MouseEventArgs e) { pPoint = Cursor.Position; } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll //当鼠标左键按下时才触发 if (e.Button == MouseButtons.Left) { MoveControl.DrawDragBound(this.currentControl); cPoint = Cursor.Position; //获得当前鼠标位置 int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y + y); pPoint = cPoint; } } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void MouseUp(object sender, MouseEventArgs e) { this.currentControl.Refresh(); } #endregion } }
下面我们来测试下拖动的功能。
创建一个Form窗体,可以再界面上添加你要测试的控件类型,此处我只用TextBox左下测试。在Load的中添加以下代码,将Form中的所有控件挂载上拖拉功能。
private void Form1_Load(object sender, EventArgs e) { foreach (Control ctrl in this.Controls) { new MoveControl(ctrl); } }
此时,有心人可能会发现VS中拖动控件时,将会出现黑色边框,而处于没有。
这也很简单,我们在MouseMove时加上如下代码即可。
/// <summary> /// 绘制拖拉时的黑色边框 /// </summary> public static void DrawDragBound(Control ctrl) { ctrl.Refresh(); Graphics g = ctrl.CreateGraphics(); int width = ctrl.Width; int height = ctrl.Height; Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0), new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)}; g.DrawLines(new Pen(Color.Black), ps); } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll //当鼠标左键按下时才触发 if (e.Button == MouseButtons.Left) { MoveControl.DrawDragBound(this.currentControl); cPoint = Cursor.Position; //获得当前鼠标位置 int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y + y); pPoint = cPoint; } }
同时要在MoveUp的时候,刷新一下自己,让黑色边框消失掉!
/// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void MouseUp(object sender, MouseEventArgs e) { this.currentControl.Refresh(); }
接着用没有边框的控件测试下就会很明显。如下图所示:
2.通过边框拖拉控件改变大小
此处的主要思路为:点击控件的时候,创建一个自定义的用户控件,该用户控件响应区域就是传入控件的边框区域,同时给它画上虚线与8个小圆圈。
第一、创建用户控件--FrameControl(边框控件),然后增加一个字段用来保存传入的控件,还有加载事件,此处类同前面的MoveControl。
FrameControl
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; namespace DragControl { public partial class FrameControl : UserControl { #region Constructors public FrameControl(Control ctrl) { baseControl = ctrl; AddEvents(); } #endregion #region Fields Control baseControl; //基础控件,即被包围的控件 #endregion #region Methods /// <summary> /// 加载事件 /// </summary> private void AddEvents() { this.Name = "FrameControl" + baseControl.Name; this.MouseDown += new MouseEventHandler(FrameControl_MouseDown); this.MouseMove += new MouseEventHandler(FrameControl_MouseMove); this.MouseUp += new MouseEventHandler(FrameControl_MouseUp); } #endregion #region Events /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void FrameControl_MouseDown(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void FrameControl_MouseMove(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void FrameControl_MouseUp(object sender, MouseEventArgs e) { } #endregion } }
做完这些准备工作后,将到了主要的部分,就是给控件画边框。
整个边框分为三个部分:四边框(用来设置可视区域与区域)+四条虚线(只用来显示)+八个小圆圈(用来斜角拖拉)。
所以要建立三个字段,用来分别保存这个数据。
Rectangle[] smallRects = new Rectangle[8];//边框中的八个小圆圈 Rectangle[] sideRects = new Rectangle[4];//四条边框,用来做响应区域 Point[] linePoints = new Point[5];//四条边,用于画虚线
接着就是创建用户控件的可视区域,和上面的三个变量数值。
(以下计算位置的代码,有兴趣的人可以研究下,没有的就直接Copy)
创建边框
#region 创建边框 /// <summary> /// 建立控件可视区域 /// </summary> private void CreateBounds() { //创建边界 int X = baseControl.Bounds.X - square.Width - 1; int Y = baseControl.Bounds.Y - square.Height - 1; int Height = baseControl.Bounds.Height + (square.Height * 2) + 2; int Width = baseControl.Bounds.Width + (square.Width * 2) + 2; this.Bounds = new Rectangle(X, Y, Width, Height); this.BringToFront(); SetRectangles(); //设置可视区域 this.Region = new Region(BuildFrame()); g = this.CreateGraphics(); } /// <summary> /// 设置定义8个小矩形的范围 /// </summary> void SetRectangles() { //左上 smallRects[0] = new Rectangle(new Point(0, 0), square); //右上 smallRects[1] = new Rectangle(new Point(this.Width - square.Width - 1, 0), square); //左下 smallRects[2] = new Rectangle(new Point(0, this.Height - square.Height - 1), square); //右下 smallRects[3] = new Rectangle(new Point(this.Width - square.Width - 1, this.Height - square.Height - 1), square); //上中 smallRects[4] = new Rectangle(new Point(this.Width / 2 - 1, 0), square); //下中 smallRects[5] = new Rectangle(new Point(this.Width / 2 - 1, this.Height - square.Height - 1), square); //左中 smallRects[6] = new Rectangle(new Point(0, this.Height / 2 - 1), square); //右中 smallRects[7] = new Rectangle(new Point(square.Width + baseControl.Width + 1, this.Height / 2 - 1), square); //四条边线 //左上 linePoints[0] = new Point(square.Width / 2, square.Height / 2); //右上 linePoints[1] = new Point(this.Width - square.Width / 2 - 1, square.Height / 2); //右下 linePoints[2] = new Point(this.Width - square.Width / 2 - 1, this.Height - square.Height / 2); //左下 linePoints[3] = new Point(square.Width / 2, this.Height - square.Height / 2 - 1); //左上 linePoints[4] = new Point(square.Width / 2, square.Height / 2); //整个包括周围边框的范围 ControlRect = new Rectangle(new Point(0, 0), this.Bounds.Size); } /// <summary> /// 设置边框控件可视区域 /// </summary> /// <returns></returns> private GraphicsPath BuildFrame() { GraphicsPath path = new GraphicsPath(); //上边框 sideRects[0] = new Rectangle(0, 0, this.Width - square.Width - 1, square.Height + 1); //左边框 sideRects[1] = new Rectangle(0, square.Height + 1, square.Width + 1, this.Height - square.Height - 1); //下边框 sideRects[2] = new Rectangle(square.Width + 1, this.Height - square.Height - 1, this.Width - square.Width - 1, square.Height + 1); //右边框 sideRects[3] = new Rectangle(this.Width - square.Width - 1, 0, square.Width + 1, this.Height - square.Height - 1); path.AddRectangle(sideRects[0]); path.AddRectangle(sideRects[1]); path.AddRectangle(sideRects[2]); path.AddRectangle(sideRects[3]); return path; } #endregion
设置完位置后,接着就是绘制的工作。增加一个Draw的方法用来画,同时设置为Public。此处不用控件的Paint,而是让用户调用,只因为这样方便在不同控件之间切换,也就是一个容器中,只有当前控件有边框。
/// <summary> /// 绘图 /// </summary> public void Draw() { this.BringToFront(); Pen pen = new Pen(Color.Black); pen.DashStyle = DashStyle.Dot;//设置为虚线,用虚线画四边,模拟微软效果 g.DrawLines(pen, linePoints);//绘制四条边线 g.FillRectangles(Brushes.White, smallRects); //填充8个小矩形的内部 foreach (Rectangle smallRect in smallRects) { g.DrawEllipse(Pens.Black, smallRect); //绘制8个小椭圆 } //g.DrawRectangles(Pens.Black, smallRects); //绘制8个小矩形的黑色边线 }
做到这里,我们可以去前台看一下效果,不过再此之前,我们需要调用该用户控件。
调用的地方就是在控件上点击的时候,所以在MoveControl中加入MouseClick的事件。
/// <summary> /// 鼠标单击事件:用来显示边框 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void MouseClick(object sender, MouseEventArgs e) { this.currentControl.Parent.Refresh();//刷新父容器,清除掉其他控件的边框 this.currentControl.BringToFront(); fc = new FrameControl(this.currentControl); this.currentControl.Parent.Controls.Add(fc); fc.Visible = true; fc.Draw(); }
这时有了边框之后会有一个小问题,就是拖动控件的时候,控件移动了,但是边框还留在原地。
所以,这里需要注意的,就是移动的时候,将边框控件隐藏掉,当MouseUp的时候再显示。
此时的完整代码如下:
MoveControl
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; namespace DragControl { public class MoveControl { #region Constructors public MoveControl(Control ctrl) { currentControl = ctrl; AddEvents(); } #endregion #region Fields private Control currentControl; //传入的控件 private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标 FrameControl fc;//边框控件 #endregion #region Properties #endregion #region Methods /// <summary> /// 挂载事件 /// </summary> private void AddEvents() { currentControl.MouseClick += new MouseEventHandler(MouseClick); currentControl.MouseDown += new MouseEventHandler(MouseDown); currentControl.MouseMove += new MouseEventHandler(MouseMove); currentControl.MouseUp += new MouseEventHandler(MouseUp); } /// <summary> /// 绘制拖拉时的黑色边框 /// </summary> public static void DrawDragBound(Control ctrl) { ctrl.Refresh(); Graphics g = ctrl.CreateGraphics(); int width = ctrl.Width; int height = ctrl.Height; Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0), new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)}; g.DrawLines(new Pen(Color.Black), ps); } #endregion #region Events /// <summary> /// 鼠标单击事件:用来显示边框 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void MouseClick(object sender, MouseEventArgs e) { this.currentControl.Parent.Refresh();//刷新父容器,清除掉其他控件的边框 this.currentControl.BringToFront(); fc = new FrameControl(this.currentControl); this.currentControl.Parent.Controls.Add(fc); fc.Visible = true; fc.Draw(); } /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void MouseDown(object sender, MouseEventArgs e) { pPoint = Cursor.Position; } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll //当鼠标左键按下时才触发 if (e.Button == MouseButtons.Left) { MoveControl.DrawDragBound(this.currentControl); if (fc != null) fc.Visible = false; //先隐藏 cPoint = Cursor.Position; //获得当前鼠标位置 int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y + y); pPoint = cPoint; } } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void MouseUp(object sender, MouseEventArgs e) { this.currentControl.Refresh(); if (fc != null) { fc.Visible = true; fc.Draw(); } } #endregion } }
FrameControl
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; using System.Drawing.Drawing2D; namespace DragControl { public partial class FrameControl : UserControl { #region Constructors public FrameControl(Control ctrl) { baseControl = ctrl; AddEvents(); CreateBounds(); } #endregion #region Fields const int Band = 6; //调整大小的响应边框 Size square = new Size(Band, Band);//小矩形大小 Control baseControl; //基础控件,即被包围的控件 Rectangle[] smallRects = new Rectangle[8];//边框中的八个小圆圈 Rectangle[] sideRects = new Rectangle[4];//四条边框,用来做响应区域 Point[] linePoints = new Point[5];//四条边,用于画虚线 Graphics g; //画图板 Rectangle ControlRect; //控件包含边框的区域 #endregion #region Methods /// <summary> /// 加载事件 /// </summary> private void AddEvents() { this.Name = "FrameControl" + baseControl.Name; this.MouseDown += new MouseEventHandler(FrameControl_MouseDown); this.MouseMove += new MouseEventHandler(FrameControl_MouseMove); this.MouseUp += new MouseEventHandler(FrameControl_MouseUp); } #region 创建边框 /// <summary> /// 建立控件可视区域 /// </summary> private void CreateBounds() { //创建边界 int X = baseControl.Bounds.X - square.Width - 1; int Y = baseControl.Bounds.Y - square.Height - 1; int Height = baseControl.Bounds.Height + (square.Height * 2) + 2; int Width = baseControl.Bounds.Width + (square.Width * 2) + 2; this.Bounds = new Rectangle(X, Y, Width, Height); this.BringToFront(); SetRectangles(); //设置可视区域 this.Region = new Region(BuildFrame()); g = this.CreateGraphics(); } /// <summary> /// 设置定义8个小矩形的范围 /// </summary> void SetRectangles() { //左上 smallRects[0] = new Rectangle(new Point(0, 0), square); //右上 smallRects[1] = new Rectangle(new Point(this.Width - square.Width - 1, 0), square); //左下 smallRects[2] = new Rectangle(new Point(0, this.Height - square.Height - 1), square); //右下 smallRects[3] = new Rectangle(new Point(this.Width - square.Width - 1, this.Height - square.Height - 1), square); //上中 smallRects[4] = new Rectangle(new Point(this.Width / 2 - 1, 0), square); //下中 smallRects[5] = new Rectangle(new Point(this.Width / 2 - 1, this.Height - square.Height - 1), square); //左中 smallRects[6] = new Rectangle(new Point(0, this.Height / 2 - 1), square); //右中 smallRects[7] = new Rectangle(new Point(square.Width + baseControl.Width + 1, this.Height / 2 - 1), square); //四条边线 //左上 linePoints[0] = new Point(square.Width / 2, square.Height / 2); //右上 linePoints[1] = new Point(this.Width - square.Width / 2 - 1, square.Height / 2); //右下 linePoints[2] = new Point(this.Width - square.Width / 2 - 1, this.Height - square.Height / 2); //左下 linePoints[3] = new Point(square.Width / 2, this.Height - square.Height / 2 - 1); //左上 linePoints[4] = new Point(square.Width / 2, square.Height / 2); //整个包括周围边框的范围 ControlRect = new Rectangle(new Point(0, 0), this.Bounds.Size); } /// <summary> /// 设置边框控件可视区域 /// </summary> /// <returns></returns> private GraphicsPath BuildFrame() { GraphicsPath path = new GraphicsPath(); //上边框 sideRects[0] = new Rectangle(0, 0, this.Width - square.Width - 1, square.Height + 1); //左边框 sideRects[1] = new Rectangle(0, square.Height + 1, square.Width + 1, this.Height - square.Height - 1); //下边框 sideRects[2] = new Rectangle(square.Width + 1, this.Height - square.Height - 1, this.Width - square.Width - 1, square.Height + 1); //右边框 sideRects[3] = new Rectangle(this.Width - square.Width - 1, 0, square.Width + 1, this.Height - square.Height - 1); path.AddRectangle(sideRects[0]); path.AddRectangle(sideRects[1]); path.AddRectangle(sideRects[2]); path.AddRectangle(sideRects[3]); return path; } #endregion /// <summary> /// 绘图 /// </summary> public void Draw() { this.BringToFront(); Pen pen = new Pen(Color.Black); pen.DashStyle = DashStyle.Dot;//设置为虚线,用虚线画四边,模拟微软效果 g.DrawLines(pen, linePoints);//绘制四条边线 g.FillRectangles(Brushes.White, smallRects); //填充8个小矩形的内部 foreach (Rectangle smallRect in smallRects) { g.DrawEllipse(Pens.Black, smallRect); //绘制8个小椭圆 } //g.DrawRectangles(Pens.Black, smallRects); //绘制8个小矩形的黑色边线 } #endregion #region Events /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void FrameControl_MouseDown(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void FrameControl_MouseMove(object sender, MouseEventArgs e) { } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void FrameControl_MouseUp(object sender, MouseEventArgs e) { } #endregion } }
测试界面:
到目前为止,还只是有边框,下面将实现拖拉功能。
首先来实现,当鼠标放在响应区域的时候,根据不同的位置显示不同的箭头样子。
为此先创建一个枚举,用来记录当前鼠标的位置,等拖拉的时候根据该枚举值做不同的计算。
/// <summary> /// 鼠标在控件中位置 /// </summary> enum MousePosOnCtrl { NONE = 0, TOP = 1, RIGHT = 2, BOTTOM = 3, LEFT = 4, TOPLEFT = 5, TOPRIGHT = 6, BOTTOMLEFT = 7, BOTTOMRIGHT = 8, }
创建一个方法,用来改变光标的样子以及枚举值
/// <summary> /// 设置光标状态 /// </summary> public bool SetCursorShape(int x, int y) { Point point = new Point(x, y); if (!ControlRect.Contains(point)) { Cursor.Current = Cursors.Arrow; return false; } else if (smallRects[0].Contains(point)) { Cursor.Current = Cursors.SizeNWSE; mpoc = MousePosOnCtrl.TOPLEFT; } else if (smallRects[1].Contains(point)) { Cursor.Current = Cursors.SizeNESW; mpoc = MousePosOnCtrl.TOPRIGHT; } else if (smallRects[2].Contains(point)) { Cursor.Current = Cursors.SizeNESW; mpoc = MousePosOnCtrl.BOTTOMLEFT; } else if (smallRects[3].Contains(point)) { Cursor.Current = Cursors.SizeNWSE; mpoc = MousePosOnCtrl.BOTTOMRIGHT; } else if (sideRects[0].Contains(point)) { Cursor.Current = Cursors.SizeNS; mpoc = MousePosOnCtrl.TOP; } else if (sideRects[1].Contains(point)) { Cursor.Current = Cursors.SizeWE; mpoc = MousePosOnCtrl.LEFT; } else if (sideRects[2].Contains(point)) { Cursor.Current = Cursors.SizeNS; mpoc = MousePosOnCtrl.BOTTOM; } else if (sideRects[3].Contains(point)) { Cursor.Current = Cursors.SizeWE; mpoc = MousePosOnCtrl.RIGHT; } else { Cursor.Current = Cursors.Arrow; } return true; }
接着就是处理相关的三大事件MouseDown、MouseMove、MouseUp来实现拖拉。如同MoveControl都要增加以下两个字段。
private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标
/// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void FrameControl_MouseDown(object sender, MouseEventArgs e) { pPoint = Cursor.Position; } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void FrameControl_MouseMove(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.Visible = false; MoveControl.DrawDragBound(baseControl); ControlMove(); } else { this.Visible = true; SetCursorShape(e.X, e.Y); //更新鼠标指针样式 } } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void FrameControl_MouseUp(object sender, MouseEventArgs e) { this.baseControl.Refresh(); //刷掉黑色边框 this.Visible = true; CreateBounds(); Draw(); }
在上面的MouseMove中多了一个方法--ControlMove,这个就是根据不同的枚举值,计算控件的移动方式和大小的方法。该方法中同时对控件的最小宽度和高度做了处理。添加如下两个字段。
private int MinWidth = 20; //最小宽度 private int MinHeight = 20;//最小高度
/// <summary> /// 控件移动 /// </summary> private void ControlMove() { cPoint = Cursor.Position; int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; switch (this.mpoc) { case MousePosOnCtrl.TOP: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } break; case MousePosOnCtrl.BOTTOM: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } break; case MousePosOnCtrl.LEFT: if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.RIGHT: if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; case MousePosOnCtrl.TOPLEFT: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.TOPRIGHT: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; case MousePosOnCtrl.BOTTOMLEFT: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.BOTTOMRIGHT: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; } pPoint = Cursor.Position; }
到此为止,功能已经基本上实现。
完成代码如下:
MoveControl
/****************************************************************** * 创 建 人: SamWang * 创建时间: 2012-5-10 16:06 * 描 述: * 移动控件但不改变大小 * 原 理: * 版 本: V1.0 * 环 境: VS2010 ******************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; namespace DragControl { public class MoveControl { #region Constructors public MoveControl(Control ctrl) { currentControl = ctrl; AddEvents(); } #endregion #region Fields private Control currentControl; //传入的控件 private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标 FrameControl fc;//边框控件 #endregion #region Properties #endregion #region Methods /// <summary> /// 挂载事件 /// </summary> private void AddEvents() { currentControl.MouseClick += new MouseEventHandler(MouseClick); currentControl.MouseDown += new MouseEventHandler(MouseDown); currentControl.MouseMove += new MouseEventHandler(MouseMove); currentControl.MouseUp += new MouseEventHandler(MouseUp); } /// <summary> /// 绘制拖拉时的黑色边框 /// </summary> public static void DrawDragBound(Control ctrl) { ctrl.Refresh(); Graphics g = ctrl.CreateGraphics(); int width = ctrl.Width; int height = ctrl.Height; Point[] ps = new Point[5]{new Point(0,0),new Point(width -1,0), new Point(width -1,height -1),new Point(0,height-1),new Point(0,0)}; g.DrawLines(new Pen(Color.Black), ps); } #endregion #region Events /// <summary> /// 鼠标单击事件:用来显示边框 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void MouseClick(object sender, MouseEventArgs e) { this.currentControl.Parent.Refresh();//刷新父容器,清除掉其他控件的边框 this.currentControl.BringToFront(); fc = new FrameControl(this.currentControl); this.currentControl.Parent.Controls.Add(fc); fc.Visible = true; fc.Draw(); } /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void MouseDown(object sender, MouseEventArgs e) { pPoint = Cursor.Position; } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void MouseMove(object sender, MouseEventArgs e) { Cursor.Current = Cursors.SizeAll; //当鼠标处于控件内部时,显示光标样式为SizeAll //当鼠标左键按下时才触发 if (e.Button == MouseButtons.Left) { MoveControl.DrawDragBound(this.currentControl); if(fc != null ) fc.Visible = false; //先隐藏 cPoint = Cursor.Position;//获得当前鼠标位置 int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; currentControl.Location = new Point(currentControl.Location.X + x, currentControl.Location.Y + y); pPoint = cPoint; } } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void MouseUp(object sender, MouseEventArgs e) { this.currentControl.Refresh(); if (fc != null) { fc.Visible = true; fc.Draw(); } } #endregion } }
FrameControl
/****************************************************************** * 创 建 人: SamWang * 创建时间: 2012-5-10 17:00 * 描 述: * 在控件外部加上边框,用于拖拉,以改变内部控件的大小 * 原 理: * 版 本: V1.0 * 环 境: VS2010 ******************************************************************/ using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Drawing.Drawing2D; namespace DragControl { public class FrameControl : UserControl { #region Constructors /// <summary> /// 构造函数 /// </summary> public FrameControl(Control ctrl) { baseControl = ctrl; AddEvents(); CreateBounds(); } #endregion #region Fields const int Band = 6; //调整大小的响应边框 private int MinWidth = 20; //最小宽度 private int MinHeight = 20;//最小高度 Size square = new Size(Band, Band);//小矩形大小 Control baseControl; //基础控件,即被包围的控件 Rectangle[] smallRects = new Rectangle[8];//边框中的八个小圆圈 Rectangle[] sideRects = new Rectangle[4];//四条边框,用来做响应区域 Point[] linePoints = new Point[5];//四条边,用于画虚线 Graphics g; //画图板 Rectangle ControlRect; //控件包含边框的区域 private Point pPoint; //上个鼠标坐标 private Point cPoint; //当前鼠标坐标 private MousePosOnCtrl mpoc; #endregion #region Properties /// <summary> /// 鼠标在控件中位置 /// </summary> enum MousePosOnCtrl { NONE = 0, TOP = 1, RIGHT = 2, BOTTOM = 3, LEFT = 4, TOPLEFT = 5, TOPRIGHT = 6, BOTTOMLEFT = 7, BOTTOMRIGHT = 8, } #endregion #region Methods /// <summary> /// 加载事件 /// </summary> private void AddEvents() { this.Name = "FrameControl" + baseControl.Name; this.MouseDown += new MouseEventHandler(FrameControl_MouseDown); this.MouseMove += new MouseEventHandler(FrameControl_MouseMove); this.MouseUp += new MouseEventHandler(FrameControl_MouseUp); } #region 创建边框 /// <summary> /// 建立控件可视区域 /// </summary> private void CreateBounds() { //创建边界 int X = baseControl.Bounds.X - square.Width - 1; int Y = baseControl.Bounds.Y - square.Height - 1; int Height = baseControl.Bounds.Height + (square.Height * 2) + 2; int Width = baseControl.Bounds.Width + (square.Width * 2) + 2; this.Bounds = new Rectangle(X, Y, Width, Height); this.BringToFront(); SetRectangles(); //设置可视区域 this.Region = new Region(BuildFrame()); g = this.CreateGraphics(); } /// <summary> /// 设置定义8个小矩形的范围 /// </summary> void SetRectangles() { //左上 smallRects[0] = new Rectangle(new Point(0, 0), square); //右上 smallRects[1] = new Rectangle(new Point(this.Width - square.Width - 1, 0), square); //左下 smallRects[2] = new Rectangle(new Point(0, this.Height - square.Height - 1), square); //右下 smallRects[3] = new Rectangle(new Point(this.Width - square.Width - 1, this.Height - square.Height - 1), square); //上中 smallRects[4] = new Rectangle(new Point(this.Width / 2 - 1, 0), square); //下中 smallRects[5] = new Rectangle(new Point(this.Width / 2 - 1, this.Height - square.Height - 1), square); //左中 smallRects[6] = new Rectangle(new Point(0, this.Height / 2 - 1), square); //右中 smallRects[7] = new Rectangle(new Point(square.Width + baseControl.Width + 1, this.Height / 2 - 1), square); //四条边线 //左上 linePoints[0] = new Point(square.Width / 2, square.Height / 2); //右上 linePoints[1] = new Point(this.Width - square.Width / 2 - 1, square.Height / 2); //右下 linePoints[2] = new Point(this.Width - square.Width / 2 - 1, this.Height - square.Height / 2); //左下 linePoints[3] = new Point(square.Width / 2, this.Height - square.Height / 2 - 1); //左上 linePoints[4] = new Point(square.Width / 2, square.Height / 2); //整个包括周围边框的范围 ControlRect = new Rectangle(new Point(0, 0), this.Bounds.Size); } /// <summary> /// 设置边框控件可视区域 /// </summary> /// <returns></returns> private GraphicsPath BuildFrame() { GraphicsPath path = new GraphicsPath(); //上边框 sideRects[0] = new Rectangle(0, 0, this.Width - square.Width - 1, square.Height + 1); //左边框 sideRects[1] = new Rectangle(0, square.Height + 1, square.Width + 1, this.Height - square.Height - 1); //下边框 sideRects[2] = new Rectangle(square.Width + 1, this.Height - square.Height - 1, this.Width - square.Width - 1, square.Height + 1); //右边框 sideRects[3] = new Rectangle(this.Width - square.Width - 1, 0, square.Width + 1, this.Height - square.Height - 1); path.AddRectangle(sideRects[0]); path.AddRectangle(sideRects[1]); path.AddRectangle(sideRects[2]); path.AddRectangle(sideRects[3]); return path; } #endregion /// <summary> /// 绘图 /// </summary> public void Draw() { this.BringToFront(); //g.FillRectangles(Brushes.LightGray, sideRects); //填充四条边框的内部 Pen pen = new Pen(Color.Black); pen.DashStyle = DashStyle.Dot;//设置为虚线,用虚线画四边,模拟微软效果 g.DrawLines(pen, linePoints);//绘制四条边线 g.FillRectangles(Brushes.White, smallRects); //填充8个小矩形的内部 foreach (Rectangle smallRect in smallRects) { g.DrawEllipse(Pens.Black, smallRect); //绘制8个小椭圆 } //g.DrawRectangles(Pens.Black, smallRects); //绘制8个小矩形的黑色边线 } /// <summary> /// 设置光标状态 /// </summary> public bool SetCursorShape(int x, int y) { Point point = new Point(x, y); if (!ControlRect.Contains(point)) { Cursor.Current = Cursors.Arrow; return false; } else if (smallRects[0].Contains(point)) { Cursor.Current = Cursors.SizeNWSE; mpoc = MousePosOnCtrl.TOPLEFT; } else if (smallRects[1].Contains(point)) { Cursor.Current = Cursors.SizeNESW; mpoc = MousePosOnCtrl.TOPRIGHT; } else if (smallRects[2].Contains(point)) { Cursor.Current = Cursors.SizeNESW; mpoc = MousePosOnCtrl.BOTTOMLEFT; } else if (smallRects[3].Contains(point)) { Cursor.Current = Cursors.SizeNWSE; mpoc = MousePosOnCtrl.BOTTOMRIGHT; } else if (sideRects[0].Contains(point)) { Cursor.Current = Cursors.SizeNS; mpoc = MousePosOnCtrl.TOP; } else if (sideRects[1].Contains(point)) { Cursor.Current = Cursors.SizeWE; mpoc = MousePosOnCtrl.LEFT; } else if (sideRects[2].Contains(point)) { Cursor.Current = Cursors.SizeNS; mpoc = MousePosOnCtrl.BOTTOM; } else if (sideRects[3].Contains(point)) { Cursor.Current = Cursors.SizeWE; mpoc = MousePosOnCtrl.RIGHT; } else { Cursor.Current = Cursors.Arrow; } return true; } /// <summary> /// 控件移动 /// </summary> private void ControlMove() { cPoint = Cursor.Position; int x = cPoint.X - pPoint.X; int y = cPoint.Y - pPoint.Y; switch (this.mpoc) { case MousePosOnCtrl.TOP: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } break; case MousePosOnCtrl.BOTTOM: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } break; case MousePosOnCtrl.LEFT: if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.RIGHT: if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; case MousePosOnCtrl.TOPLEFT: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.TOPRIGHT: if (baseControl.Height - y > MinHeight) { baseControl.Top += y; baseControl.Height -= y; } else { baseControl.Top -= MinHeight - baseControl.Height; baseControl.Height = MinHeight; } if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; case MousePosOnCtrl.BOTTOMLEFT: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } if (baseControl.Width - x > MinWidth) { baseControl.Left += x; baseControl.Width -= x; } else { baseControl.Left -= MinWidth - baseControl.Width; baseControl.Width = MinWidth; } break; case MousePosOnCtrl.BOTTOMRIGHT: if (baseControl.Height + y > MinHeight) { baseControl.Height += y; } else { baseControl.Height = MinHeight; } if (baseControl.Width + x > MinWidth) { baseControl.Width += x; } else { baseControl.Width = MinWidth; } break; } pPoint = Cursor.Position; } #endregion #region Events /// <summary> /// 鼠标按下事件:记录当前鼠标相对窗体的坐标 /// </summary> void FrameControl_MouseDown(object sender, MouseEventArgs e) { pPoint = Cursor.Position; } /// <summary> /// 鼠标移动事件:让控件跟着鼠标移动 /// </summary> void FrameControl_MouseMove(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.Visible = false; MoveControl.DrawDragBound(baseControl); ControlMove(); } else { this.Visible = true; SetCursorShape(e.X, e.Y); //更新鼠标指针样式 } } /// <summary> /// 鼠标弹起事件:让自定义的边框出现 /// </summary> void FrameControl_MouseUp(object sender, MouseEventArgs e) { this.baseControl.Refresh(); //刷掉黑色边框 this.Visible = true; CreateBounds(); Draw(); } #endregion } }
四、遗留问题
1.ListBox存在拖拉高度时,存在莫名奇妙的BUG。
2.目前该版本只支持单控件的拖拉,多控件同时拖拉等下次有空再弄。
以上这篇C# 实现拖拉控件改变位置与大小的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持易盾网络。