7

我在 Winform 上的两个对象之间绘制了一个箭头。

确定我的鼠标当前悬停在这条线上或附近的最简单方法是什么。

我考虑过测试鼠标点是否与由两个点定义和外推的正方形相交,但是这只有在两个点具有非常相似的 x 或 y 值时才可行。

我也在想,这个问题可能更多地属于线性代数而不是简单的三角学,虽然我确实记得矩阵的简单方面,但这个问题超出了我对线性代数的了解。

另一方面,如果 .NET 库能够处理该功能,那就更好了。

编辑 感谢您的回答,有一些非常好的都值得被标记为已回答。

我选择了 Coincoin 的答案作为接受,因为我喜欢它可以应用于绘制的任何形状,但最终实现了 Tim Robinson 的方程,因为使用简单的方程而不是更新图形路径和笔似乎更有效,如我的情况是,我需要为 1-n 个不同的关系在 onMouseMove 上执行此操作(显然会有一些缓存和优化,但重点仍然存在)

该方程的主要问题是它似乎将这条线视为无限,所以我也添加了一个边界测试。

对于那些感兴趣的人,代码(最初的剪辑,我可能会稍微整理一下)如下

    if (Math.Sqrt( Math.Pow(_end.X - _start.X, 2) + 
           Math.Pow(_end.Y - _start.Y, 2) ) == 0)
    {
        _isHovering =
            new RectangleF(e.X, e.Y, 1, 1).IntersectsWith(_bounds);
    }
    else
    {
        float threshold = 10.0f;

        float distance = (float)Math.Abs( 
            ( ( (_end.X - _start.X) * (_start.Y - e.Y) ) -
            ( (_start.X - e.X) * (_end.Y - _start.Y) ) ) /
            Math.Sqrt( Math.Pow(_end.X - _start.X, 2) + 
            Math.Pow(_end.Y - _start.Y, 2) ));

        _isHovering = (
            distance <= threshold &&
                new RectangleF(e.X, e.Y, 1, 1).IntersectsWith(_bounds)
            );
    }

并且 _bounds 定义为:

    _bounds = new Rectangle(
    Math.Min(_start.X, _end.X),
    Math.Min(_start.Y, _end.Y),
    Math.Abs(_start.X - _end.X), Math.Abs(_start.Y - _end.Y));
4

5 回答 5

7

如果您想轻松地对任意绘制的形状进行命中测试,您可以创建一个包含您的绘图的路径,然后加宽路径并仅使用框架函数进行可见性测试。

例如,在这里我们创建一条带有一行的路径:

GraphicsPath path = new GraphicsPath();

path.AddLine(x1, y1, x2, y2);
path.CloseFigure();

然后,加宽路径并为命中测试创建一个区域:

path.Widen(new Pen(Color.Black, 3));
region = new Region(path);

最后,命中测试:

region.IsVisible(point);

这种方法的优点是它可以很容易地扩展到样条线、箭头、弧线、饼图或几乎任何可以用 GDI+ 绘制的东西。通过提取相同的路径可以在HitTest和逻辑中使用。Draw

这是结合所有这些的代码:

public GraphicsPath Path
{
    get { 
        GraphicsPath path = new GraphicsPath();
        path.AddLine(x1, y1, x2, y2);
        path.CloseFigure();

        return path;
    }
}

bool HitTest(Point point)
{
    using(Pen new pen = Pen(Color.Black, 3))
    using(GraphicsPaht path = Path)
    {
        path.Widen(pen);

        using(Region region = new Region(path))
            return region.IsVisible(point);
    }
}


void Draw(Graphics graphics)
{
    using(Pen pen = new Pen(Color.Blue, 0))
    using(GraphicsPaht path = Path)
        graphics.DrawPath(pen, path);
}
于 2010-03-19T20:33:36.200 回答
4

要回答“鼠标悬停在这条线上吗?”,您需要检查点线相交。但是,由于您要问“鼠标靠近线吗?”,听起来您想计算鼠标点和线之间的距离。

这是对点线距离的合理彻底的解释:http: //mathworld.wolfram.com/Point-LineDistance2-Dimensional.html

我想说你需要在你的代码中实现这个公式:(从 wolfram.com 偷来的)

在哪里:

  • (x0, x0) 是鼠标指针的位置
  • (x1, y1) 是行的一端
  • (x2, y2) 是线的另一端
  • |n|Math.Abs(n)
  • 下半部分是Math.Sqrt
  • |v.r|如果你愿意,你可以忽略
于 2010-03-19T20:25:38.517 回答
2

我会为我的线计算斜率截距方程(y = mx + b),然后用它来测试鼠标坐标。您可以轻松地在 y 周围设置一个范围,以查看您是否“接近”。

编辑样本。

我认为这样的工作:

PointF currentPoint;
PointF p1, p2;
float threshold = 2.0f;
float m = (p1.Y - p2.Y) / (p1.X - p2.X);
float b = p1.Y - (m * p1.X);

if (Math.Abs(((m * currentPoint.X) + b) - currentPoint.Y) <= threshold)
{
    //On it.
}
于 2010-03-19T20:29:18.817 回答
1

您需要构建两条平行于理想路径的(名义上的)边界线。然后你只需要计算,对于每个鼠标位置,鼠标是在这些线形成的通道的外部还是内部。

无需计算鼠标到主线的距离。

于 2010-03-19T20:26:22.143 回答
0

查看 MouseEnter(object sender, EventArgs e)。当它“进入”控制区域时进行陷阱。

于 2010-03-19T20:24:42.643 回答