4

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

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

基本上:
1. 如果我有大约 50 个矩形,我如何确定我的鼠标在哪个矩形上,同时考虑到性能。
2、windows是怎么做到的?(我比什么都好奇,但如果不是很复杂,也许我可以在自己的程序中实现类似的东西?)

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

4

5 回答 5

7

在明确一段代码会造成真正的瓶颈之前,我不会太在意性能。假设您遇到了这样的瓶颈,并测量以下代码的性能(它在 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);
}

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

经过时间:701 毫秒。(每次迭代 0.701us)

如您所见,测试 50 个矩形只需要不到一微秒的时间。与创建花哨的悬停效果以及您的程序所做的其他任何事情相比,您真的认为这太长了吗?当然,只有你能回答这个问题。

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

于 2012-03-02T15:23:10.937 回答
1

不要考虑性能。如果你这样做,然后测量它!

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

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

于 2012-03-02T14:07:27.333 回答
1

这里的其他问题没有回答你的第 2 部分,所以我试一试:

2、windows是怎么做到的?(我比什么都好奇,但如果不是很复杂,也许我可以在自己的程序中实现类似的东西?)

要意识到的是,即使您打开了数十个窗口,每个窗口都有很多工具栏,每个窗口都有很多项目,等等,每次移动鼠标时,窗口都不需要检查所有内容

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

当鼠标移动时,Windows 首先确定要发送 WM_MOUSEMOVE 的正确 HWND。它通过遍历 HWND 来实现。HWND 存储为一棵树,表示包含,兄弟之间的顺序表示 Z-Order,因此 Windows 可以对这棵树进行简单的深度优先下降,以在任何给定点找出最底部的 HWND。如果您启动 Spy++ 应用程序,您可以自己查看这个 HWND 树的样子。请注意,Windows 并没有进行完全详尽的遍历:在遍历顶级应用程序窗口时,例如,为了找出该点在哪个应用程序中,一旦 windows 找到包含该点的第一个顶级 HWND,它将深入研究,完全忽略其下方/之后的所有其他应用程序 - 以及其中的所有控件。

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

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

另一个要记住的重要问题是,这一切都很快:移动鼠标发生在“人类时间”中,现代 CPU 可以在鼠标移动几个像素的时间内完成大量操作。屏幕。最后,请注意,当您在屏幕上将鼠标从 A 点移动到 B 点时,鼠标并不总是遍历其间的每个像素——尤其是当您快速移动鼠标时。

于 2012-03-04T02:49:45.940 回答
0

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

我猜 Windows 也差不多。显然它不需要测试子窗口,除非鼠标指针在父窗口中,即使是设计最糟糕的对话框也很少有超过一百个控件同时可见。具有大量命中测试区域(例如 ListView、网格)的控件会优化它们自己的命中测试。

如果您有数以万计的矩形,那么性能可能是一个问题,您可以使用此处描述的方法之一。

于 2012-03-03T16:08:37.600 回答
0

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

return ... ? true : false;

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

PS:像 ++var 和 var++ 之类的东西也会对性能产生有价值的影响,具体取决于它在代码中的使用方式(因为优化器会捕获其中一些并为您修复它......

PPS:当我这样做时......也永远不要在循环中放置表达式或方法调用,除非循环更改表达式结果,例如:for(int i=0; i < getWidth(); ++i).. . 如果这循环了一百万次,你的方法将被调用一百万次:)

于 2012-03-31T14:48:10.503 回答