Search code examples
winapimfc

Moving the mouse blocks WM_TIMER and WM_PAINT


In an application I'm working on, in some cases the application runs pretty slowly and in these cases I've discovered that my moving the mouse, timer/paint messages are not being handled. If I move the mouse in a slow circle, I can indefinitely prevent the window being re-painted!

I found that this is expected behaviour:

With the exception of the WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, the system always posts messages at the end of a message queue. This ensures that a window receives its input messages in the proper first in, first out (FIFO) sequence. The WM_PAINT message, the WM_TIMER message, and the WM_QUIT message, however, are kept in the queue and are forwarded to the window procedure only when the queue contains no other messages. In addition, multiple WM_PAINT messages for the same window are combined into a single WM_PAINT message, consolidating all invalid parts of the client area into a single area. Combining WM_PAINT messages reduces the number of times a window must redraw the contents of its client area.

However, what can I do about this? Sometimes in direct response to a mouse-move, I need to re-paint as quickly as possible.

I catch messages through a method like this on my CWnd-derived class:

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


Solution

  • The WM_PAINT and WM_TIMER, as you read in the docs, are different from other messages.

    The practical difference is that they are lowest priority messages. That is, they will not be processed if there are any other messages in the queue.

    That said, you are moving the mouse, so a lot of messages are being posted, but the frequency of these messages is usually quite low (a few dozens a second) so your program should be mostly idle, But it is not, probably because some of these messages is taking much more time than expected and it is choking the queue. You just have to detect which one and why.

    Some of the mouse-related messages, out of my mind:

    • WM_MOUSEMOVE
    • WM_NCMOUSEMOVE
    • WM_SETCURSOR
    • WM_NCHITTEST

    And from a search in the web:

    • WM_MOUSEOVER
    • WM_MOUSELEAVE
    • WM_NCMOUSEOVER
    • WM_NCMOUSELEAVE

    Anyway, if you want to repaint immediately, without waiting for a WM_PAINT you should call UpdateWindow(). This function forces an immediate processing of WM_PAINT (if there is anything invalidated, of course) and blocks until it finishes, bypassing the low priority issue of that message.

    UPDATE: According to your situation from your comments I think that your best solution may be something along these lines:

    1. In WM_MOUSEMOVE save the cursor position in a member variable and set a flag meaning that the mouse has moved.
    2. Implement an OnIdle() handler that checks the mouse moved flag. If not moved, do nothing. If moved, then do the expensive calculation.
    3. You may try it with or without calling UpdateWindow() from OnIdle() and see which one is better.

    Yes, it will still feel choppy in slow computers, but since OnIdle() has even lower priority than WM_TIMER and WM_PAINT, these messages will not be relegated indefinitely. And more importantly, you will not be queuing multiple calls to the expensive function.