WinForm拖拽控件,运行时模仿窗体设计调整控件大小和位置,上传GitHub了,也上传到NuGet

506 阅读10分钟

目录

一,预览

二,起因

三,完善

四,分析

1.窗体设计时的控件样式

2.为控件绑定事件

五,制作边框控件FrameControl

1.设置控件区域

2.绘制虚线

3.绘制实线

4.改变鼠标状态

5.调整控件大小

6.为边框控件绑定事件

六,写成扩展方法ControlExtensions

七,使用

八,NuGet搜索WinForm.MoveControl

九,源码

一,预览

先看看效果


效果还行吧...

二,起因

最近,有几个小伙伴私信我关于WinForm的问题,关于拖拽控件,就像在窗体设计时调整控件大小和位置一样。

其实在之前就写个相关的文章,2019年,哈哈,已经有两年了,之前也有发源码,但是可能还是写的不是很清晰,代码也有bug,不完善。

这是之前写的文章 【WinForm】运行时模仿窗体设计调整控件大小和位置

所以就完善了一下代码,并上传GitHub,这样可以随时更新。

被迫上传GitHub,之前上传到CSDN的资源上,系统的动态调整积分,现在积分都需要50才能下载了,被骂了,说买的这么贵。真冤,明明是系统动态调整的。现在再看看上传资源,新上传的资源已经可以手动设置所需积分,不受系统调整了,但也不上传了,直接上GitHub...

这是之前上传的资源 WinForm运行时模仿窗体设计调整控件大小和位置

对比之前的代码,有几个地方做了完善,也解决了上次遗留的问题

三,完善

与上个项目比较,完善的地方

  1. 解决了调整控件大小时,鼠标光标会变成默认状态
  2. 完善边框控件,可设置边框透明,边框覆盖其它控件
  3. 规范代码,模块化,添加详细的注释说明,上传GitHub

这回免费了,不用积分了,直接上GitHub,下面给资源地址

四,分析

虽然之前写个一篇文章,现在重写,很多功能都是类似的,部分完善了,部分还是相同的

1.窗体设计时的控件样式

首先,我们先看看,窗体设计时的控件样式

点击控件时会显示4条虚线和8个小矩形,但是同时我们为了绘制虚线和矩形,需要多设置4条底边

移动控件时会显示4条实线

2.为控件绑定事件

实现运行时调整控件大小和位置,只需为控件绑定MouseDown,MouseClick,MouseMove,MouseUp事件即可
MouseDown:鼠标键按下时,显示灰色实线
MouseClick:鼠标单击时,显示4条虚线和8个小矩形
MouseMove:鼠标拖动控件时,显示灰色实线,当鼠标点击控件时也会执行该事件
MouseUp:鼠标键释放时,显示4条虚线和8个小矩形

我们可以用GDI+绘图技术,绘制控件的灰色实线和虚线

我们先制作边框控件,只需绑定MouseDown,MouseMove,MouseUp事件即可
MouseDown:鼠标键按下时,记录鼠标位置
MouseMove:鼠标在控件上移动时,即在调整控件大小,刷新4条灰色实线,改变鼠标光标并调整控件大小
MouseUp:鼠标键释放时,刷新4条虚线

还可以绑定MouseLeave事件,鼠标离开控件时隐藏边框,这个可选

边框控件包含边框的4条底边,4条灰色实线,4条虚线和8个小矩形

五,制作边框控件FrameControl

边框控件首先需要继承UserControl

绑定MouseDown,MouseMove,MouseUp事件

接下来编写几个重要的方法

  • 设置控件区域:4条底边,点击控件后显示
  • 绘制虚线:4条虚线和8个小矩形,这个需要在OnPaint调用
  • 绘制实线:4条实线,移动控件和调整控件大小时显示
  • 改变鼠标状态,鼠标在控件上,下,左,右,左上,右上,左下,右下,向上不同的光标
  • 调整控件大小:鼠标在控件中的不同位置,调整控件大小

绘制虚线这个方法需要在OnPaint调用,原因是为了实现边框控件的透明,边框控件不会覆盖其它控件

需要在边框控件FrameControl设置控件支持透明

/// <summary>
/// 设置控件支持透明
/// </summary>
protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x20;
        return cp;
    }
}

1.设置控件区域

设置边框控件显示并置顶,设置4条底边为控件的区域

/// <summary>
/// 设置控件区域:4条底边,点击控件后显示
/// </summary>
public void SetControlRegion()
{
    //显示边框并置顶
    Visible = true;
    BringToFront();

    #region 设置控件区域

    int x = control.Bounds.X - smallRectSize.Width;
    int y = control.Bounds.Y - smallRectSize.Height;
    int width = control.Bounds.Width + (smallRectSize.Width * 2);
    int height = control.Bounds.Height + (smallRectSize.Height * 2);
    Bounds = new Rectangle(x, y, width, height);

    //包括边框的局域
    controlRect = new Rectangle(new Point(0, 0), Bounds.Size);

    #endregion

    #region 4条底边

    GraphicsPath path = new GraphicsPath();

    //上底边
    borderRects[0] = new Rectangle(0, 0, Width + size * 2, smallRectSize.Height + 1);
    //左底边
    borderRects[1] = new Rectangle(0, size + 1, smallRectSize.Width + 1, Height - size * 2 - 2);
    //下底边
    borderRects[2] = new Rectangle(0, Height - size - 1, Width + size * 2, smallRectSize.Height + 1);
    //右底边
    borderRects[3] = new Rectangle(Width - size - 1, size + 1, smallRectSize.Width + 1, Height - size * 2 - 2);
    path.AddRectangle(borderRects[0]);
    path.AddRectangle(borderRects[1]);
    path.AddRectangle(borderRects[2]);
    path.AddRectangle(borderRects[3]);

    Region = new Region(path);

    #endregion
}

2.绘制虚线

绘制4条虚线和8个小矩形

/// <summary>
/// 绘制虚线:4条虚线和8个小矩形
/// </summary>
public void DrawDottedLines(Graphics g)
{
    #region 4条虚线

    //左上
    linePoints[0] = new Point(3, 3);
    //右上
    linePoints[1] = new Point(Width - 3 - 1, 3);
    //右下
    linePoints[2] = new Point(Width - 3 - 1, Height - 3 - 1);
    //左下
    linePoints[3] = new Point(3, Height - 3 - 1);
    //左上
    linePoints[4] = new Point(3, 3);

    Pen pen = new Pen(Color.Black, 1) { DashStyle = DashStyle.Dot };

    g.DrawLines(pen, linePoints);

    #endregion

    #region  8个小矩形

    //左上
    smallRects[0] = new Rectangle(new Point(0, 0), smallRectSize);
    //右上
    smallRects[1] = new Rectangle(new Point(Width - size - 1, 0), smallRectSize);
    //左下
    smallRects[2] = new Rectangle(new Point(0, Height - size - 1), smallRectSize);
    //右下
    smallRects[3] = new Rectangle(new Point(Width - size - 1, Height - size - 1), smallRectSize);
    //上中
    smallRects[4] = new Rectangle(new Point(Width / 2 - 1, 0), smallRectSize);
    //下中
    smallRects[5] = new Rectangle(new Point(Width / 2 - 1, Height - size - 1), smallRectSize);
    //左中
    smallRects[6] = new Rectangle(new Point(0, Height / 2 - size / 2), smallRectSize);
    //右中
    smallRects[7] = new Rectangle(new Point(Width - size - 1, Height / 2 - size / 2), smallRectSize);

    //填充矩形内部为白色
    g.FillRectangles(Brushes.White, smallRects);
    //绘制矩形
    g.DrawRectangles(Pens.Black, smallRects);

    #endregion
}

3.绘制实线

绘制4条实线,移动控件和调整控件大小时显示

/// <summary>
/// 绘制实线:4条实线,移动控件和调整控件大小时显示
/// </summary>
public void DrawSolids()
{
    //隐藏边框
    Visible = false;

    Graphics g = control.CreateGraphics();
    int width = control.Width;
    int height = control.Height;
    Point[] points = 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.Gray, 3), points);
}

4.改变鼠标状态

先定义一个枚举,存储鼠标在控件中的位置

/// <summary>
/// 鼠标在控件中的位置
/// </summary>
enum MousePos
{
    None,
    Top,
    Right,
    Bottom,
    Left,
    LeftTop,
    LeftBottom,
    RightTop,
    RightBottom
}

/// <summary>
/// 鼠标在控件中的位置
/// </summary>
MousePos mousePos;

/// <summary>
/// 改变鼠标状态,鼠标在控件上,下,左,右,左上,右上,左下,右下,向上不同的光标
/// </summary>
private void SetCursor(int x, int y)
{
    Point point = new Point(x, y);

    if (!controlRect.Contains(point))//不在边框局域内直接退出
    {
        Cursor.Current = Cursors.Arrow;
        return;
    }
    else if (smallRects[0].Contains(point))//左上
    {
        Cursor.Current = Cursors.SizeNWSE;
        mousePos = MousePos.LeftTop;
    }
    else if (smallRects[1].Contains(point))//右上
    {
        Cursor.Current = Cursors.SizeNESW;
        mousePos = MousePos.RightTop;
    }
    else if (smallRects[2].Contains(point))//左下
    {
        Cursor.Current = Cursors.SizeNESW;
        mousePos = MousePos.LeftBottom;
    }
    else if (smallRects[3].Contains(point))//右下
    {
        Cursor.Current = Cursors.SizeNWSE;
        mousePos = MousePos.RightBottom;
    }

    else if (borderRects[0].Contains(point))//上
    {
        Cursor.Current = Cursors.SizeNS;
        mousePos = MousePos.Top;
    }
    else if (borderRects[1].Contains(point))//左
    {
        Cursor.Current = Cursors.SizeWE;
        mousePos = MousePos.Left;
    }
    else if (borderRects[2].Contains(point))//下
    {
        Cursor.Current = Cursors.SizeNS;
        mousePos = MousePos.Bottom;
    }
    else if (borderRects[3].Contains(point))//右
    {
        Cursor.Current = Cursors.SizeWE;
        mousePos = MousePos.Right;
    }
    else
    {
        Cursor.Current = Cursors.Arrow;
    }
}

5.调整控件大小

鼠标在控件中的不同位置,调整控件大小

/// <summary>
/// 调整控件大小:鼠标在控件中的不同位置,调整控件大小
/// </summary>
private void ReCtrlSize()
{
    //获取当前鼠标位置
   Point  currentPoint = Cursor.Position;
    int x = currentPoint.X - lastPoint.X;
    int y = currentPoint.Y - lastPoint.Y;
    switch (mousePos)
    {
        case MousePos.None:
            break;
        case MousePos.Top://上,调整
            if (control.Height - y > MinHeight)
            {
                control.Top += y;
                control.Height -= y;
            }
            break;
        case MousePos.Right:
            if (control.Width + x > MinWeight)
            {
                control.Width += x;
            }
            break;
        case MousePos.Bottom:
            if (control.Height + y > MinHeight)
            {
                control.Height += y;
            }
            break;
        case MousePos.Left:
            if (control.Width - x > MinWeight)
            {
                control.Left += x;
                control.Width -= x;
            }
            break;
        case MousePos.LeftTop://左上
            if (control.Width - x > MinWeight)
            {
                control.Left += x;
                control.Width -= x;
            }
            if (control.Height - y > MinHeight)
            {
                control.Top += y;
                control.Height -= y;
            }
            break;
        case MousePos.LeftBottom:
            if (control.Width - x > MinWeight)
            {
                control.Left += x;
                control.Width -= x;
            }
            if (control.Height + y > MinHeight)
            {
                control.Height += y;
            }
            break;
        case MousePos.RightTop:
            if (control.Width + x > MinWeight)
            {
                control.Width += x;
            }
            if (control.Height - y > MinHeight)
            {
                control.Top += y;
                control.Height -= y;
            }
            break;
        case MousePos.RightBottom:
            if (control.Width + x > MinWeight)
            {
                control.Width += x;
            }
            if (control.Height + y > MinHeight)
            {
                control.Height += y;
            }
            break;
        default:
            break;
    }
    lastPoint = Cursor.Position;
}

比如,当鼠标处于控件的下方时,调整控件的Height属性即可

6.为边框控件绑定事件

在MouseDown事件中添加SetCursor就解决了调整控件大小时,鼠标光标会变成默认状态的问题,现在调整大小,鼠标光标也会是箭头状态

#region 绑定事件

//鼠标按下时
MouseDown += (sender, e) =>
{
    //记录鼠标位置
    lastPoint = Cursor.Position;

    //改变鼠标状态
    SetCursor(e.X, e.Y);
};

//鼠标移动时,即在调整控件大小
MouseMove += (sender, e) =>
{
    //鼠标左键
    if (e.Button == MouseButtons.Left)
    {
        //移动时刷新实线
        DrawSolids();

        //调整控件大小
        ReCtrlSize();
    }
    //不是鼠标左键,则表示只是在控件上移动
    else
    {
        //改变鼠标状态
        SetCursor(e.X, e.Y);
    }
};

//鼠标键释放时
MouseUp += (sender, e) =>
{
    control.Refresh();

    //显示虚线
    SetControlRegion();
};

////鼠标离开控件隐藏边框
//MouseLeave += (sender, e) =>
//{
//    this.Visible = false;
//};

边框控件部分就完成了,接下来就是为所需控件添加边框控件,但是如果每个控件都绑定,重复代码太多,那就写成扩展方法方便调用

六,写成扩展方法ControlExtensions

为所需控件绑定MouseDown,MouseClick,MouseMove,MouseUp事件

public static class ControlExtensions
{
    /// <summary>
    /// 设置控件移动和调整大小
    /// </summary>
    public static void SetMove(this Control control)
    {
        //边框控件
        FrameControl fControl = null;
        //上一个鼠标坐标
        Point lastPoint = new Point();

        #region 绑定事件

        //鼠标键按下时
        control.MouseDown += (sender, e) =>
        {
            //记录鼠标坐标
            lastPoint = Cursor.Position;

            //清除所有控件的边框区域,最主要的是清除上次点击控件的边框,恢复原来状态
            foreach (Control ctrl in control.Parent.Controls)
                if (ctrl is FrameControl)
                    ctrl.Visible = false;
            if (fControl == null)
                fControl = new FrameControl(control);
            //设置边框背景色为透明,可以设置其它颜色
            fControl.BackColor = Color.Transparent;
            //把边框控件添加到当前控件的父控件中
            control.Parent.Controls.Add(fControl);
        };

        //鼠标单击时
        control.MouseClick += (sender, e) =>
        {
            control.BringToFront();
        };

        //鼠标在控件上移动时
        control.MouseMove += (sender, e) =>
        {
            Point currentPoint = new Point();
            Cursor.Current = Cursors.SizeAll;
            if (e.Button == MouseButtons.Left)
            {
                currentPoint = Cursor.Position;
                control.Location = new Point(control.Location.X + currentPoint.X - lastPoint.X,
                    control.Location.Y + currentPoint.Y - lastPoint.Y);

                //移动时刷新实线
                fControl.DrawSolids();

                control.BringToFront();
            }

            lastPoint = currentPoint;
        };

        //鼠标键释放时
        control.MouseUp += (sender, e) =>
        {
            //设置控件区域
            fControl.SetControlRegion();
        };

        #endregion
    }
}

七,使用

使用起来很简单,在Form的Load事件中设置,只需一句代码

button1.SetMove();

效果

完成...完美...

八,NuGet搜索WinForm.MoveControl

我把项目制作成nuget包,上传到nuget上了,在NuGet搜索WinForm.MoveControl安装

或使用命令

Install-Package WinForm.MoveControl -Version 1.0.5

如何制作成NuGet包,看另外一篇文章:

greambwang.blog.csdn.net/article/det…

最后上GitHub,拿源码,记得给个Star一下

九,源码

GitHub:github.com/GreAmbWang/…

nuget:www.nuget.org/packages/Wi…