如何实现可选中、拖拽的圆形?请完善我的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
相关产品推荐
相关产品推荐

