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

5
在一个C#的Windows Forms应用程序中,是否可以使用鼠标选择、移动或删除绘制的图形?就像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();
}

我可以使用基本的线性代数让它计算鼠标位置与每条线之间的最短距离,并设置一个阈值距离,如果小于阈值,则选择此线,并且可以通过鼠标拖动或编辑。但是,我想知道是否有更可管理的方法来完成这个任务?主要是选择部分。 感谢您的建议!


1
你不需要线性代数。你可以使用 GraphicsPath.IsVisible 来检查一个点是否在路径中。 - Reza Aghaei
1个回答

10

不需要线性代数就可以进行形状的命中测试。您可以为形状创建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。为了让你更简单地选择线条,我添加了两个点进行命中测试:

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);
    }
}

DrawingSurface

该控件可以绘制一系列形状,并且能够在MouseDown事件中执行命中测试,如果拖动则可移动形状。您应该将一些形状如LineCircle添加到控件的Shapes集合中。

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);
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接