【问题标题】:Moving the mouse blocks WM_TIMER and WM_PAINT移动鼠标块 WM_TIMER 和 WM_PAINT
【发布时间】:2014-03-29 03:56:54
【问题描述】:

在我正在开发的应用程序中,在某些情况下应用程序运行速度很慢,在这些情况下,我发现我的鼠标移动、计时器/绘画消息没有得到处理。如果我慢速移动鼠标,我可以无限期地阻止重新绘制窗口!

我发现这是expected behaviour

除了 WM_PAINT 消息、WM_TIMER 消息和 WM_QUIT 消息,系统总是在一个结束时发布消息 消息队列。这确保了一个窗口接收到它的输入消息 以正确的先进先出 (FIFO) 顺序。 WM_PAINT 消息、WM_TIMER 消息和 WM_QUIT 消息是 保留在队列中并仅在以下情况下转发到窗口过程 队列不包含其他消息。此外,多个 WM_PAINT 同一窗口的消息组合成一个 WM_PAINT 消息,将客户区的所有无效部分合并到一个 单一区域。组合 WM_PAINT 消息可减少 窗口必须重绘其客户区的内容。

但是,我能做些什么呢?有时为了直接响应鼠标移动,我需要尽快重新绘制。

我通过这样的方法在我的 CWnd 派生类上捕获消息:

virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

【问题讨论】:

  • 如果您需要立即重新绘制,请致电InvalidateRect,然后拨打UpdateWindow,或使用RedrawWindow 标记RDW_UPDATENOW
  • 我想知道当收到WM_MOUSEMOVE时,是否可以像while(msg == WM_MOUSEMOVE)一样轮询消息队列,只处理最后一条WM_MOUSEMOVE消息,避免类似消息的堆叠。
  • 简单的解决方案是让您更快地处理WM_MOUSEMOVE
  • 遗憾的是,这不是一个简单的解决方案。
  • 您可以在循环中调用PeekMessage() 以检索队列中的任何WM_MOUSEMOVE 消息。如果您忽略WM_MOUSEMOVE 中的坐标并使用GetCursorPos() 自己读取光标位置,您可能还会发现获得更好的结果。

标签: winapi mfc


【解决方案1】:

您在文档中看到的WM_PAINTWM_TIMER 与其他消息不同。

实际的区别在于它们是最低优先级的消息。也就是说,如果队列中有任何其他消息,它们将不会被处理。

也就是说,您正在移动鼠标,因此发布了很多消息,但是这些消息的频率通常很低(每秒几十个),因此您的程序应该大部分是空闲的,但事实并非如此,可能是因为其中一些消息花费的时间比预期的要长得多,并且阻塞了队列。您只需要检测哪一个以及为什么。

一些与鼠标有关的消息,我想不通:

  • WM_MOUSEMOVE
  • WM_NCMOUSEMOVE
  • WM_SETCURSOR
  • WM_NCHITTEST

通过网络搜索:

  • WM_MOUSEOVER
  • WM_MOUSELEAVE
  • WM_NCMOUSEOVER
  • WM_NCMOUSELEAVE

无论如何,如果您想立即重新绘制,而无需等待WM_PAINT,您应该致电UpdateWindow()。此函数强制立即处理 WM_PAINT(当然,如果有任何无效的内容)并阻塞直到它完成,绕过该消息的低优先级问题。

更新:根据您的 cmets 的情况,我认为您的最佳解决方案可能是这样的:

  1. WM_MOUSEMOVE 中将光标位置保存在成员变量中,并设置一个标志,表示鼠标已移动。
  2. 实现一个检查鼠标移动标志的OnIdle() 处理程序。如果不动,什么也不做。如果搬家,则进行昂贵的计算。
  3. 您可以尝试使用或不使用OnIdle() 中的UpdateWindow(),看看哪个更好。

是的,在慢速计算机中仍然会感觉不稳定,但由于OnIdle() 的优先级甚至低于WM_TIMERWM_PAINT,因此这些消息不会无限期地降级。更重要的是,您不会对昂贵函数的多次调用进行排队。

【讨论】:

  • 它基本上是鼠标移动功能 - 我们检测鼠标下方的内容(第三部分库也是如此)。这是一个地图应用程序,如果它是地图中非常繁忙的部分,那么速度较慢的 PC 就会将所有时间都花在这上面。可悲的是,没有简单的解决方法可以加快速度,我们只需要在所有情况下停止它优先。
  • 如果您在 WM_MOUSEMOVE 中花费了太多时间以至于阻塞了消息队列,那么您应该考虑使用工作线程。你的 GUI 不应该做这么繁重的工作。
  • @John:假设您每 10 毫秒收到一次 WM_MOUSEMOVE,但处理它需要 25 毫秒:您有问题。如果您使用UpdateWindow() 修复它,那么更新将落后于鼠标。如果您不执行UpdateWindow(),则在鼠标停止之前不会发生重绘...一个工作线程并在每隔几个点之一执行该工作可能是您最好的解决方案。
  • 我认为目前这种重新设计的水平是不可行的,尤其是当我们将鼠标移动事件输入到一个封闭源代码的第 3 方库中时。如果在 25 毫秒内收到的任何 WM_MOUSEMOVE 事件被简单地丢弃,那将是完全可以的,但我不知道 CWnd::WindowProc() 是否可以从队列中“吸”消息?在用于游戏编程的普通 Win32 中,您经常会这样做 - 每一帧您都会弹出消息,直到队列为空,然后 然后 渲染。 无论这是否是一种好的做法,都欢迎提供证明它的答案
  • @John:请看我更新的答案是否适合您的问题。
【解决方案2】:

在几乎所有情况下,正确的解决方案是,当您更新应用程序状态以响应 WM_MOUSEMOVE 时需要重新绘制,您应该调用 UpdateWindow()。这就对了。您不应该使用 PeekMessage()、GetCursorPos、OnIdle() 等。

如果您调用 UpdateWindow(),那么将强制发送 WM_PAINT 消息并且您的窗口将被更新,用户将看到对他们的鼠标移动的反应,并且您的应用程序将感到响应。这很好。事实上,这很理想。

如果您不调用 UpdateWindow(),则在传递 WM_PAINT 消息之前可能会出现另一条鼠标消息。大多数鼠标可以以 128/s 的速度传递 WM_MOUSEMOVE 消息,触摸屏似乎以 200/s 的速度运行,而游戏鼠标可能更快。因此,如果用户快速移动鼠标,那么如果不强制,WM_PAINT 消息可能永远不会有机会被传递。这使您处于每秒处理 100+ WM_MOUSEMOVE 消息但从不绘制窗口的情况。这意味着您的 internal 帧速率为 100 fps,可见 帧速率为 0 fps。坏了。

因此,再一次,答案是在需要绘制的 WM_MOUSEMOVE 处理之后调用 UpdateWindow()。您的内部帧率可能较低(因为现在您每条消息都需要做更多的工作),但您的可见帧率现在将与您的内部帧率匹配,重要的是您的可见帧率。

额外的 WM_MOUSEMOVE 消息将被悄悄丢弃,如 http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx 所述

我已经对许多应用程序进行了此修复,并且结果一直很出色。这一单行修复通常会使应用程序的响应速度提高 10 倍。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-04
    • 2011-01-19
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多