Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Capturing the Mouse

0.00/5 (No votes)
3 Mar 2000 1  
Notes on TrackMouseEvent, and SetCapture on Win32.

Introduction

The implementation of the UI of an application sometimes requires the capture of the mouse. The following situations come to mind:

  • You need a reliable mouse over detection.
  • You are implementing some sort of drag and drop interface.
  • You want to know what window the mouse is over.


Notes on the SetCapture API

The action of the SetCapture() API is somewhat complex, and not well documented in the Platform SDK. You can unserstand how best to use SetCapture() in you application if you understand the following limitations of using mouse capture:

Only one window can have the mouse capture at a time. A window can request mouse capture by calling the SetCapture() API, and that window has the mouse capture untill either the ReleaseCapture() API or SetCapture() is called directing the mouse capture to a diffrent window.

In addition, there are two types of capture, that I call foreground and background capture. Foreground capture is obtained when the follwoing two conditions are met:

  • The current thread is the foreground thread (ie. it owns the foreground window).
  • At least one mouse button is being held down.

Otherwise (if the current thread is not the foreground thread, or no mouse buttons are held down) the window merely gets background capture.

If, at any time, all the mouse buttons are released, the mouse capture will automatically revert to the bacground.

Here are the diffrences between foreground and background capture:

Foreground Capture
A window with foreground capture receives all mouse messages for all windows in the system.

Background Capture
Once the capture has reverted to background capture, the window only receives mouse messages for:
  • all windows owned by the same thread
  • all windows on all threads if those windows and the window with capture share the same top-level window.


Implementing a Drag operation using SetCapture

To implement a drag operation in your application you would implement the following message handlers:

WM_LBUTTONDOWN
WM_RBUTTONDOWN
A drag operation generally starts when the user clicks on something, and begins to move the mouse. If the user is clicking on something draggable use the DragDetect() API to dectect if a drag operation is beginning. Once the beginning of a drag operation is confirmed call SetCapture(). Note that various drag enabled controls detect when a drag begins and send their parent a message such as LVN_BEGINDRAG.
WM_LBUTTONUP
WM_RBUTTONUP
If the mouse up finishes the drag (see the below Remarks for more info on why a mouse up might not finish a drag operation) ReleaseCapture() must be called to allow other windows access to mouse messages.
WM_CHAR A drag operation can ususally be aborted by pressing ESC. If required abort the drag operation and call ReleaseCapture().
WM_CANCELMODE The window maanger sends this message when it detects a change that requires that an application cancel any modal state it has entered. Abort the drag operation and call ReleaseCapture().
WM_CAPTURECHANGED The capture has been cleared, or some other window has obtained it. It probably makes no sense to continue your drag operation, so it should be aborted. As you have explicitly lost capture you don't need to call ReleaseCapture().
WM_SETCURSOR Mouse capture interrupts the normal flow of mouse processing. WM_SETCURSOR messages are not dispatched to a window that has mouse capture - if the cursor should be set to indicate the drag via SetCursor when the drag operation begins - if the cursor needs to change to provide feedback to the user it should be set in resoponse to WM_MOUSEMOVE.


Remarks

With the exception of the mouse down or initial drag operation begin detection most applications implement their drag code in a modal loop to prevent cluttering up the main applications window procedure.

Also note that most system drag operations allows the user to "swap" buttons during a drag by pressing the other button, and then releasing the initial button. If the drag operation is not implemented in a modal loop this situation would ahve to be specially catered for to prevent another drag operation being launched.


Using TrackMouseEvent

There are two variations of this API available:

TrackMouseEvent()
Available as a standard window manager function on Windows 98 and above and Windows NT 4 and above.

_TrackMouseEvent()
Available in the common control library on all systems with Internet Explorer 3 and higher.

Use TrackMouseEvent() if you can ignore windows 95 as a target. Use TrackMouseEvent() if you need to target windows 95, and can assume the machine has at least IE3 installed. If you need TrackMouseEvent() functionality on Windows 95 and cannot assume at least IE3 then the following quick hack demonstrates the basic functionality.


Rolling your own TrackMouseEvent

A full custom implementation of TrackMouseEvent() would have to implement a message hook so it could hook messages intended for any window. Any window that needs to detect mouse enter, leave or hover events can use code like this:

#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500

  case WM_MOUSEMOVE:
    SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
    break;
  case WM_TIMER:
    RECT rc;
    POINT pt;
    GetWindowRect(hwnd,&rc);
    GetCursorPos(&pt);
    if(PtInRect(&rc,pt))
    {
      PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
      break;
    }
    PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
    KillTimer(hwnd,TID_POLLMOUSE);
    break;

A more responsive version could be made by using SetCapture() to detect more quickly when the mouse leaves.

#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500

  case WM_MOUSEMOVE:
    RECT rc;
    POINT pt;
    GetWindowRect(hwnd,&rc);
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
    if(PtInRect(&rc,pt))
    {
      SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
      if(hwnd != GetCapture())
      {
        SetCapture(hwnd);
        PostMessage(hwnd,WM_MOUSEENTER,0,0L);
      }
      break;
    }
    ReleaseCapture();
    KillTimer(hwnd,TID_POLLMOUSE);
    PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
    break;
  case WM_TIMER:
    GetWindowRect(hwnd,&rc);
    GetCursorPos(&pt);
    if(PtInRect(&rc,pt))
    {
      PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
      break;
    }
    ReleaseCapture();
    KillTimer(hwnd,TID_POLLMOUSE);
    PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
    break;

The code samples provided differ in a number of ways from the TrackMouseEvent() APIs:

  • In a real application you need to perfrom hover detection for child windows. The sample assumes you are perfroming hover detection over the main window only.
  • TrackMouseEvent() does not send a WM_MOUSEENTER message. Nor is there such a message defined. The code simply demonstrates how such a message could be implemented.
  • TrackMouseEvent is a once off API. Once a notification has been received you must call it again to receive subsequent notifications. The sample code given will repeatedly send WM_MOUSEHOVER events - no mechanism is provided whereby notifications can be stopped, or only arrive when requested.

Please feel free to expand the code to fit your own requirements.

Please send any comments or bug reports to me via email. For any updates to this article, check my site here.


Knowledge Base References

More information on this topic can be found in the following KB articles:

Q135865 HOWTO: Use Win32 API to Draw a Dragging Rectangle on Screen DC
Q183107 HOWTO: Detect When the Cursor Leaves the Window

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here