You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可选中、拖拽的圆形?请完善我的C# WinForms代码

完善圆形选中、拖拽与碰撞检测功能

以下是针对需求优化后的代码,包含正确的点击命中检测、选中状态切换、拖拽功能以及基础碰撞检测逻辑:

1. 优化CircleItem类

public class CircleItem
{
    public bool Selected { get; set; }
    public Color CircleColor { get; set; }
    public Point StartPoint { get; set; }
    public int LineWidth { get; set; }
    public string Caption { get; set; }
    // 统一圆直径常量,方便后续维护
    private const int CircleDiameter = 25;
    public int Radius => CircleDiameter / 2;
    // 快速获取圆心坐标
    public Point Center => new Point(StartPoint.X + Radius, StartPoint.Y + Radius);

    public CircleItem(string caption, Color circleColor, Point startPoint, int lineWidth)
    {
        CircleColor = circleColor;
        StartPoint = startPoint;
        LineWidth = lineWidth;
        Caption = caption;
    }

    public void Draw(Graphics g)
    {
        // 选中状态使用差异化样式区分
        var fillBrush = new SolidBrush(Selected ? Color.LightBlue : CircleColor);
        var borderPen = new Pen(Selected ? Color.Blue : Color.DimGray, LineWidth);
        
        using (fillBrush)
        using (borderPen)
        using (var captionFont = new Font("Arial", 9))
        {
            g.FillEllipse(fillBrush, StartPoint.X, StartPoint.Y, CircleDiameter, CircleDiameter);
            g.DrawEllipse(borderPen, StartPoint.X, StartPoint.Y, CircleDiameter, CircleDiameter);
            // 计算文字居中位置
            var textSize = g.MeasureString(Caption, captionFont);
            var textX = StartPoint.X + (CircleDiameter - textSize.Width) / 2;
            var textY = StartPoint.Y + (CircleDiameter - textSize.Height) / 2;
            g.DrawString(Caption, captionFont, Brushes.Green, textX, textY);
        }
    }

    // 准确判断鼠标是否点击在圆内
    public bool HitTest(Point pt)
    {
        // 用距离平方计算替代开根号,提升性能
        int dx = pt.X - Center.X;
        int dy = pt.Y - Center.Y;
        return dx * dx + dy * dy <= Radius * Radius;
    }

    // 判断当前圆与其他圆是否碰撞
    public bool IsCollidingWith(CircleItem other)
    {
        if (other == this) return false;
        int dx = Center.X - other.Center.X;
        int dy = Center.Y - other.Center.Y;
        int distanceSquared = dx * dx + dy * dy;
        int combinedRadius = Radius + other.Radius;
        return distanceSquared <= combinedRadius * combinedRadius;
    }
}

2. 优化Form1主窗体代码

public partial class Form1 : Form
{
    private List<CircleItem> circleItems = new List<CircleItem>();
    // 当前正在拖拽的圆实例
    private CircleItem draggingCircle;
    // 鼠标按下时相对于圆左上角的偏移量
    private Point dragOffset;

    public Form1()
    {
        InitializeComponent();
        // 开启双缓冲解决绘制闪烁问题
        typeof(Panel).InvokeMember("DoubleBuffered", BindingFlags.SetProperty
            | BindingFlags.Instance | BindingFlags.NonPublic, null,
            picCanvas, new object[] { true });
        picCanvas.Paint += PicCanvas_PaintEventHandler;
    }

    private void PicCanvas_PaintEventHandler(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        foreach (CircleItem cItem in circleItems)
        {
            cItem.Draw(e.Graphics);
        }
    }

    private void picCanvas_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            // 左键点击添加新圆,让圆心对齐鼠标位置
            var circleStart = new Point(e.X - 12, e.Y - 12);
            circleItems.Add(new CircleItem(circleItems.Count.ToString(), Color.White, circleStart, 4));
            picCanvas.Invalidate();
        }
        else if (e.Button == MouseButtons.Right)
        {
            // 右键点击切换选中状态,点击空白处取消所有选中
            bool clickedOnCircle = false;
            foreach (var cir in circleItems)
            {
                if (cir.HitTest(e.Location))
                {
                    cir.Selected = !cir.Selected;
                    clickedOnCircle = true;
                }
                else
                {
                    // 单选模式:取消非点击圆的选中状态,需要多选可注释此行
                    cir.Selected = false;
                }
            }
            if (!clickedOnCircle)
            {
                foreach (var cir in circleItems) cir.Selected = false;
            }
            picCanvas.Invalidate();
        }
    }

    private void picCanvas_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            // 从后往前遍历,确保选中最上层的圆
            for (int i = circleItems.Count - 1; i >= 0; i--)
            {
                var cir = circleItems[i];
                if (cir.HitTest(e.Location))
                {
                    draggingCircle = cir;
                    dragOffset = new Point(e.X - cir.StartPoint.X, e.Y - cir.StartPoint.Y);
                    // 将选中圆移到列表末尾,确保绘制在最上层
                    circleItems.RemoveAt(i);
                    circleItems.Add(draggingCircle);
                    draggingCircle.Selected = true;
                    // 取消其他圆的选中状态
                    foreach (var otherCir in circleItems)
                    {
                        if (otherCir != draggingCircle) otherCir.Selected = false;
                    }
                    picCanvas.Invalidate();
                    break;
                }
            }
        }
    }

    private void picCanvas_MouseMove(object sender, MouseEventArgs e)
    {
        if (draggingCircle != null)
        {
            var newStart = new Point(e.X - dragOffset.X, e.Y - dragOffset.Y);
            
            // 碰撞检测:若新位置会与其他圆重叠,则禁止移动
            bool isColliding = false;
            foreach (var otherCir in circleItems)
            {
                if (otherCir != draggingCircle)
                {
                    var originalPos = draggingCircle.StartPoint;
                    draggingCircle.StartPoint = newStart;
                    if (draggingCircle.IsCollidingWith(otherCir))
                    {
                        isColliding = true;
                    }
                    draggingCircle.StartPoint = originalPos;
                    if (isColliding) break;
                }
            }

            if (!isColliding)
            {
                draggingCircle.StartPoint = newStart;
                picCanvas.Invalidate();
            }
        }
    }

    private void picCanvas_MouseUp(object sender, MouseEventArgs e)
    {
        // 结束拖拽状态
        draggingCircle = null;
    }
}

关键功能说明

  • 命中检测:采用距离平方计算,避免开根号运算,既保证准确性又提升性能
  • 选中状态:右键点击切换选中,选中圆显示浅蓝色填充+蓝色边框;默认单选模式,如需多选可注释对应取消选中的代码
  • 拖拽功能:左键按住圆即可拖拽,拖拽时圆会自动移到最上层;记录鼠标偏移量,确保拖拽过程中鼠标与圆的相对位置不变
  • 碰撞检测:拖拽时实时检测与其他圆的重叠情况,若碰撞则禁止移动,可根据需求修改为碰撞吸附、提示等逻辑

内容的提问来源于stack exchange,提问作者Mehdi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:07:03