点击测试矩形

时间:2012-03-02 14:01:30

标签: c++ windows winapi detection hit

我正在开展一个项目,我有几个矩形,我希望每个项目都有悬停效果。现在我知道我可以捕获 WM_MOUSEMOVE 消息并遍历每个矩形。但是,如果我有很多矩形(如果50很多),那该怎么办呢 我可能错了,但是不会迭代那么多,并且每次鼠标移动时都会对每个应用程序进行一次测试?

然后我开始想知道操作系统(例如windows)是如何做到这一点的,现在我的屏幕上有100多个东西,当我将鼠标悬停在它们上面时,它们都有某种动画。而且我不认为每次鼠标移动一个像素时,窗口都会遍历所有窗口。

基本上:
1.如果我有大约50个矩形,我怎么能弄清楚我的鼠标在哪个矩形上面,并考虑到性能 2. Windows如何做到这一点? (我对任何东西都更好奇,但如果它不复杂,也许我可以在我自己的程序中实现类似的东西?)

哦,它们都是矩形,它们不会旋转或任何东西。

5 个答案:

答案 0 :(得分:8)

在明确一段代码会产生真正的瓶颈之前,我不会对性能感到困扰。让我们假设你有这样的瓶颈,并测量下面代码的性能(它在C#中,但我很确定C ++不会慢):

public class Rectangle
{
    public int X { get; set; }
    public int Y { get; set; }
    public int W { get; set; }
    public int H { get; set; }

    public bool HitTest(int x, int y)
    {
        return x >= X && x < X + W && y >= Y && y < Y + H ? true : false;
    }
}

我们对HitTest()方法的表现感兴趣,所以让我们来测量它!

void PerformanceTest()
{
    const int Iterations = 1000000;
    Random rnd = new Random();
    var rectangles = Enumerable.Range(1, 50).Select(
            r => new Rectangle {
                X = rnd.Next(1000),
                Y = rnd.Next(1000),
                W = rnd.Next(1000),
                H = rnd.Next(1000)}).ToList();

    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < Iterations; i++)
    {
        rectangles.ForEach(r => r.HitTest(500, 500));
    }
    sw.Stop();

    Console.WriteLine("Elapsed time: {0}ms. ({1}us per one iteration)",
        sw.ElapsedMilliseconds,
        (float)sw.ElapsedMilliseconds * 1000 / Iterations);
}

在我的电脑上打印上面的代码:

  

经过的时间:701ms。 (每次迭代0.701us)

如您所见,点击测试50个矩形需要不到 1微秒。你真的认为这比制作花哨的悬停效果和你的程序所做的其他时间要长吗?当然,只有你能回答这个问题。

但我的故事的寓意是:不要试图预先优化,不要花时间试图解决可能根本不存在的问题。

答案 1 :(得分:2)

不要考虑性能。 如果你这样做,那就测量吧!

鼠标事件是非常低级别的事件,它很快,真的。 Windows将鼠标消息放入队列中,您的应用程序会读取或忽略它们。在鼠标事件处理程序中,检查鼠标是哪个矩形是一种快速操作。

如果你的“矩形”是windows控件(它们应该),那么你可以为每个控件设置一个鼠标监听器,以便Windows自动调用正确的处理程序。

答案 2 :(得分:0)

我同意对于少数矩形(例如50个),依次测试每个矩形的明显方法可能是最快的。

我猜想Windows的功能大致相同。显然,它不必测试子窗口,除非鼠标指针位于父窗口中,即使设计最糟糕的对话框也很少同时显示超过一百个控件。具有大量命中测试区域(例如ListViews,网格)的控件优化了他们自己的命中测试。

如果您有数万个矩形,那么性能可能会出现问题,您可以使用the methods described here之一。

答案 3 :(得分:0)

这里的其他问题没有回答你的第2部分,所以我会给出一个镜头:

2. Windows如何做到这一点? (我对任何东西都更好奇,但如果它不复杂,也许我可以在我自己的程序中实现类似的东西?)

要意识到的是,即使你打开了数十个窗口,每个窗口都有很多工具栏,每个工具栏中都有很多项目,依此类推,每次移动鼠标时,windows都不需要检查一切

Windows基本上分为两层:有HWND,这就是Windows本身管理桌面空间细分的方式;并且通常在每个HWND内是一个控制器,用于管理该HWND中的自己的空间:管理其自己的列表项的列表框,管理其自己的选项卡的选项卡控件,管理其自己的HTML页面布局的HTML控件,等等。 (或者,在您的情况下,代码管理50个左右的矩形。)

当鼠标移动时,Windows首先确定要将WM_MOUSEMOVE发送到的正确HWND。它通过遍历HWND来实现。 HWND存储为树,表示包含,以及表示Z顺序的兄弟之间的顺序,因此Windows可以在此树中进行简单的深度优先下降,以找出任何给定点的最底部HWND。如果您启动Spy ++应用程序,您可以自己查看此HWND树的外观。请注意,Windows没有进行全面的详尽遍历:例如,当遍历顶级应用程序窗口时,为了找出该点所在的应用程序,只要Windows找到包含该点的第一个顶级HWND,它就会将深入研究,彻底忽略其下/之后的所有其他应用程序 - 以及其中的所有控件。这是关键,这意味着即使屏幕上同时有很多可见数量,窗口也只需要遍历相对较少的HWND。

一旦Windows确定了正确的HWND,它就会向其发送相应的消息(WM_NCHITTEST,WM_MOUSEMOVE等),然后由该控件对其自己的内容进行同样的操作。对于包含固定大小项目的列表框,确定特定点处的项目可能与分割操作一样简单;或者对于HTML控件,控件可能有自己的等效于“布局树”,它可以用来遍历快速确定该点的元素。在您的情况下,循环遍历矩形列表可能非常好。

这是一个有点简化的版本:它比上面复杂一点 - 例如。 windows不仅仅是一个直接点检查,还有其他检查允许奇形和透明的窗口(以及不可见和禁用的窗口);但基本的树下降思想适用。

要记住的另一个重要问题是所有这一切都很快:移动鼠标发生在“人类时间”,而现代CPU可以在鼠标移动几个时间内进行大量操作屏幕上的像素。最后,请注意,当您将鼠标从A点移动到屏幕上的B点时,鼠标并不总是遍历它们之间的每个像素 - 特别是如果您快速移动鼠标。

答案 4 :(得分:0)

我相信你在答案中有一个完全不必要的“分支”(在你的测试中会产生额外的百万次操作)。在你的'HitTest'中你以:

结尾
return ... ? true : false;

“?true:false”是多余的,因为表达式已经是'true'或'false'。在尝试制作超高效的代码时,我总是想到“操作”已经执行......

PS:像++ var和var ++这样的东西可以对性能产生有价值的影响,具体取决于它在代码中的使用方式(因为优化器会抓住它们并为你修复它...

PPS:当我在它时......除非循环改变表达式结果,否则永远不会在循环中放置表达式或方法调用,例如:for(int i = 0; i&lt; getWidth(); ++ i)......如果这个循环一百万次,你的方法将被称为百万次:)