如何在 C# 中拖动和移动形状

Posted

技术标签:

【中文标题】如何在 C# 中拖动和移动形状【英文标题】:How to drag and move shapes in C# 【发布时间】:2016-12-09 09:04:06 【问题描述】:

在 C# WindoeFormsApplication 中,是否可以选择,从而用鼠标移动或删除绘制的形状?就像 windows 画图程序一样。

形状绘图工作得很好,所有点都存储在某个数组中。作为这个画线的例子

Point Latest  get; set; 

List<Point> _points = new List<Point>(); 

protected override void OnMouseMove(MouseEventArgs e)

base.OnMouseMove(e);

// Save the mouse coordinates
Latest = new Point(e.X, e.Y);

// Force to invalidate the form client area and immediately redraw itself. 
Refresh();


protected override void OnPaint(PaintEventArgs e)

var g = e.Graphics;
base.OnPaint(e);

if (_points.Count > 0)

    var pen = new Pen(Color.Navy);
    var pt = _points[0];
    for(var i=1; _points.Count > i; i++)
    
        var next = _points[i];
        g.DrawLine(pen, pt, next);
        pt = next;
    

    g.DrawLine(pen, pt, Latest);



private void Form1_MouseClick(object sender, MouseEventArgs e)

Latest = new Point(e.X, e.Y);
_points.Add(Latest);
Refresh();

我可以让它通过基本线性代数计算鼠标位置到每条线的最短距离,并设置一个阈值距离,如果小于阈值,则选中这条线,并且可以用鼠标拖动或编辑。但是,只是想知道,对于这样的任务,有什么方法更易于管理吗?主要是选择部分。 任何建议将不胜感激,谢谢!

【问题讨论】:

你不需要线性代数。您可以使用GraphicsPath.IsVisible 来检查一个点是否在路径中。 【参考方案1】:

要达到测试形状,您不需要线性代数。您可以为您的形状创建GraphicsPath,然后使用GraphicsPath.IsVisible 方法或GraphicsPath.IsOutlineVisible 方法执行命中测试。

要检查一个点是否在您的路径区域中,例如填充形状,请使用IsVisible

要对直线、曲线或空形状进行命中测试,您可以使用IsOutlineVisible

示例

例如,您可以创建一个基本的IShape 接口,其中包含用于命中测试、绘图和移动的方法。然后在类中实现这些方法。您还可以创建一个DrawingSurface 控件,它可以处理命中测试、绘制和移动IShape 对象。

在下面的示例中,我们创建了IShape 接口、LineCircle 类。我们还创建了一个DrawingSurface 控件。为了测试这个例子,只需在Form 上放置一个DrawingSurface 控件并处理Load 表单事件并添加一些形状,然后运行应用程序并尝试移动形状。

IShape

这个接口包含一些有用的方法,如果有任何类实现它们,可以用于绘图、命中测试和移动。在本示例的最后,您可以看到一个 DrawingSurface 控件,它可以简单地与 IShape 实现一起使用:

public interface IShape

    GraphicsPath GetPath();
    bool HitTest(Point p);
    void Draw(Graphics g);
    void Move(Point d);

线

这是一个实现IShape接口的行类。在点击在线进行命中测试时,HitTest 返回 true。另外为了让你选线更简单,我加了 2 分进行命中测试:

public class Line : IShape

    public Line()  LineWidth = 2; LineColor = Color.Black; 
    public int LineWidth  get; set; 
    public Color LineColor  get; set; 
    public Point Point1  get; set; 
    public Point Point2  get; set; 
    public GraphicsPath GetPath()
    
        var path = new GraphicsPath();
        path.AddLine(Point1, Point2);
        return path;
    
    public bool HitTest(Point p)
    
        var result = false;
        using (var path = GetPath())
        using (var pen = new Pen(LineColor, LineWidth + 2))
            result = path.IsOutlineVisible(p, pen);
        return result;
    
    public void Draw(Graphics g)
    
        using (var path = GetPath())
        using (var pen = new Pen(LineColor, LineWidth))
            g.DrawPath(pen, path);
    
    public void Move(Point d)
    
        Point1 = new Point(Point1.X + d.X, Point1.Y + d.Y);
        Point2 = new Point(Point2.X + d.X, Point2.Y + d.Y);
    

圈子

这是一个实现IShape接口的圆类。点击圆圈进行命中测试时,HitTest 返回 true:

public class Circle : IShape

    public Circle()  FillColor = Color.Black; 
    public Color FillColor  get; set; 
    public Point Center  get; set; 
    public int Radious  get; set; 
    public GraphicsPath GetPath()
    
        var path = new GraphicsPath();
        var p = Center;
        p.Offset(-Radious, -Radious);
        path.AddEllipse(p.X, p.Y, 2 * Radious, 2 * Radious);
        return path;
    

    public bool HitTest(Point p)
    
        var result = false;
        using (var path = GetPath())
            result = path.IsVisible(p);
        return result;
    
    public void Draw(Graphics g)
    
        using (var path = GetPath())
        using (var brush = new SolidBrush(FillColor))
            g.FillPath(brush, path);
    
    public void Move(Point d)
    
        Center = new Point(Center.X + d.X, Center.Y + d.Y);
    

绘图表面

控件,绘制形状列表。它还在MouseDown 中执行命中测试,并在拖动时移动形状。您应该在控件的Shapes 集合中添加一些形状,例如LineCircle

public class DrawingSurface : Control

    public List<IShape> Shapes  get; private set; 
    IShape selectedShape;
    bool moving;
    Point previousPoint = Point.Empty;
    public DrawingSurface()  DoubleBuffered = true; Shapes = new List<IShape>(); 
    protected override void OnMouseDown(MouseEventArgs e)
    
        for (var i = Shapes.Count - 1; i >= 0; i--)
            if (Shapes[i].HitTest(e.Location))  selectedShape = Shapes[i]; break; 
        if (selectedShape != null)  moving = true; previousPoint = e.Location; 
        base.OnMouseDown(e);
    
    protected override void OnMouseMove(MouseEventArgs e)
    
        if (moving) 
            var d = new Point(e.X - previousPoint.X, e.Y - previousPoint.Y);
            selectedShape.Move(d);
            previousPoint = e.Location;
            this.Invalidate();
        
        base.OnMouseMove(e);
    
    protected override void OnMouseUp(MouseEventArgs e)
    
        if (moving)  selectedShape = null; moving = false; 
        base.OnMouseUp(e);
    
    protected override void OnPaint(PaintEventArgs e)
    
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        foreach (var shape in Shapes)
            shape.Draw(e.Graphics);
    

【讨论】:

你也会发现这篇文章很有帮助Draw multiple freehand Polyline or Curve drawing - Adding Undo Feature 还有这个c# How do I detect if a line (painted/drawn on a form) has been clicked on when using winforms?

以上是关于如何在 C# 中拖动和移动形状的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中旋转二维对象

如何在android中动态地使形状可拖动和调整大小

可拖动图像上的画布绘图

C#怎么才能实现拖动控件时窗体也跟着移动

UIBezierPath 路径形状可点击和可拖动

如何在 d3.js 中拖动路径