TrackMouseEvent tracks mouse events in your window, but only if the events belong to your window


Greg Williams wonders why Track­Mouse­Event fails to detect mouse hover events when responding to Do­Drag­Drop callbacks. “My suspicion is that Do­Drag­Drop monopolizes the window so that a WM_MOUSEHOVER message is never posted, so it won’t end up being useful.”

That’s basically it, for the appropriate sense of the word “monopolize.”

The Track­Mouse­Event monitors mouse events that take place in your window and notifies your window when events of interest occur. But this requires that the events actually take place in your window!

The Do­Drag­Drop function calls Set­Capture so that it can carry out the task of following the mouse anywhere on the screen. Recall that mouse events normally are delivered to the window beneath the mouse, but Set­Capture lets you say, “No, I want all mouse events to go to me for as long as the mouse button is held down. Mine! All mine!” This function is typically called when you are performing some sort of mouse drag operation so that the window can respond to mouse events even after the use has dragged the mouse out of the window. (Which, in many cases, is the only interesting case.)

That’s why Track­Mouse­Event has no effect when you try to use it to detect mouse hovering during a drag/drop operation: The Track­Mouse­Event function is not seeing any mouse events! They’re all being stolen by Do­Drag­Drop.

The unfortunate consequence of this is that if you want to have a special behavior during drag/drop hover, you’ll have to detect the hover manually by remembering the mouse position and timestamp and waiting for the appropriate amount of time to elapse without a significant mouse motion.

Exercise: But wait, since I don’t get drag/drop events when the mouse is not inside my window, how can I simulate WM_MOUSELEAVE?

Comments (15)
  1. Jules says:

    IDropTarget.DragLeave would appear to be your friend, if you really need a WM_MOUSELEAVE event.  Which makes me wonder if you could send your own WM_MOUSEMOVE messages from IDropTarget.DragOver and therefore make TrackMouseEvent work correctly.

  2. SimonRev says:

    Well, this won't be the most efficient thing in the world, but certainly GetCursorPos combined with WM_TIMER would do the trick.

  3. peterchen says:

    For WM_MOUSELEAVE, I've either used SetCapture myself, or a timer-based method as already suggested by SimonRev.

    The first affects other windows (which basically miss their first WM_MOUSEMOVE, so a skilled mouser could move the mouse over another window without that window noticing), the second uses polling with all its downsides (even though the additional CPU load and "hot code pages" are minimal by odays standards).

    So yeah, is there a better method?

  4. Tihiy says:

    Set a hook on WM_MOUSEMOVE messages and watch for coordinates?

  5. peterchen says:

    Hook would work, too, but you'd have to use a system-wide hook, i.e. the code should be in a DLL that can get injected into other processes, and certain processes might be shielded from injected code.  Hm…

  6. Clipboarder says:

    Hooking ist just an awful idea. It slows the system down, doesn't work with elevated or wow32 processes and can make the whole system unstable.

    I'd try to respond to IDropTarget::DragLeave()…

  7. Tihiy says:

    I meant only current thread hook, since it's supposed my thread does start drag-and-drop? It shouldn't be costly at all.

  8. Alex Cohn says:

    I could be missing something, but polling on timer for WM_MOUSELEAVE is not a burden – you only find out that WM_MOUSEMOVE does not arrive in due time. I don't think you even need to GetCursorPos to make sure the mouse has left the window.

  9. Born without imagination says:

    In Soviet Russia, the mouse tracks you!

    Sorry, I couldn't resist…

  10. Joshua says:

    I agree, *stay away* from system wide hooking, lest your application make the BLODA.

  11. Danny says:

    Lol@…hmm, how to say this…play safe programmers?

    I guess you never wrote a good system wide hook, all you did is wrote something that got called by the system, you did ALL the code inside and then when system hung due to your callback function doing too much work you come up with the impression "boo, don't use system wide hooks, they slow down the system and bla bla bla" – learn to use them properly. I wonder how you would wrote a kernel driver..same way as you wrote that callback function for the hook?

    Learn programming properly, don't rely on "oh, hardware is sky rocketing at speed I don't need to optimize my code" crap.

    To answer to Ray exercise..system wide hook would do the trick..always.

  12. Sam says:

    or you can hook the SetCursor function and be lazy and destroy the user experience for all apps on that machine.

    I cant believe how common this is :(

  13. B Hess says:

    Devs just don't want to do the right thing because the right thing is harder to do and we all know how lazy most devs want to be.

  14. Danny says:

    @Ens

    And there you go, you answered to your own specific scenario..so still need the wide hook, ha?

  15. Ens says:

    If you're only interested in the hover during your own app's known DoDragDrop handling, then a hook, used judiciously, is alright, since you can get rid of it right away.  Running one all the time is a potential performance issue, though.  If you attach a debugger to your program, then you can forget about using your mouse on that system.  Technically it will work, but at a resolution that's too frustrating to do use beyond detaching the debugger (unless you fudge the registry setting).

    @peterchen

    You could use a low level mouse hook; then you aren't injecting code into anybody.

    @Danny

    Relying on a hook *IS* depending on the hardware to take care of your performance impact.  It's a great way to cause incredible amounts of context switching.  The documentation for hooks specifically calls out that you should use them sparingly.  I agree you could make a mouse hook work here, if you set it whenever you would call TrackMouseEvent and unset it as soon as you do the WM_MOUSELEAVE happens, thus keeping it tightly scoped to what should be a very temporary situation.

    But given that Raymond's exact question seems to be specifically about WM_MOUSELEAVE during drag/drop events ("…since I don't get <b>drag/drop events</b>…"), IDropTarget::DragLeave is a better answer here.  That's what I used when I hit this exact problem a few months back (actually, I used DragEnter, but the reasons are similar — it would make a mouse hook a terrible, terrible choice in that case).  It's a completely passive solution that doesn't require you to manually set and unset things all the time to avoid performance impact.  You need to use a hook if you want WM_MOUSELEAVE even when other applications have mouse capture for arbitrary reasons.

Comments are closed.